From 9661504e51da9d863f96e74fe51fe15f6cd2e454 Mon Sep 17 00:00:00 2001 From: Ayzen Date: Thu, 11 Jun 2026 13:02:02 +0300 Subject: [PATCH] new kamil adc --- Makefile | 29 +- README.md | 5 +- .../kamil_adc_collector/README.md | 42 + .../include/capture_file_writer.h | 55 + .../include/tty_protocol_writer.h | 41 + .../src/capture_file_writer.cpp | 1019 +++++ .../kamil_adc_collector/src/main.cpp | 3263 +++++++++++++++++ .../src/tty_protocol_writer.cpp | 450 +++ .../vendor/lcard/e502api.h | 1106 ++++++ .../vendor/lcard/lcard_pstdint.h | 810 ++++ .../vendor/lcard/x502api.h | 3129 ++++++++++++++++ .../app_window_config/profile_io_mixin.py | 1 + .../app_window_config/state_builders.py | 2 + .../controllers/app_window_pipeline_mixin.py | 2 +- .../app_window_plot/trace_plot_mixin.py | 118 +- .../app_window_preprocess_mixin.py | 24 +- .../gui/controllers/app_window_ui_mixin.py | 5 +- .../sections/processing_section.py | 11 +- .../hardware_full/kamil_adc/__init__.py | 36 + python_app/hardware_full/kamil_adc/laser.py | 103 + .../hardware_full/kamil_adc/processing.py | 185 + .../hardware_full/kamil_adc/protocol.py | 141 + python_app/hardware_full/kamil_adc/service.py | 358 ++ .../hardware_full/kamil_adc/tty_reader.py | 190 + python_app/hardware_full/kamil_adc_service.py | 639 ---- .../hardware_full/single_radar_service.py | 2 +- python_app/hardware_full/switch_service.py | 23 + python_app/models/gui_profile_codec.py | 7 + python_app/models/gui_profile_schema.py | 1 + python_app/models/run_config_codec.py | 24 + python_app/models/run_config_model.py | 4 + python_app/models/run_config_schema.py | 36 + .../orchestration/process_supervisor.py | 38 + python_app/scripts/kamil_adc_calibrate.py | 265 ++ python_app/scripts/kamil_adc_raw_producer.py | 25 +- python_app/tests/test_gui_profile_codec.py | 16 + .../test_kamil_adc_neutral_preprocess.py | 99 +- python_app/tests/test_kamil_adc_processing.py | 172 + python_app/tests/test_kamil_adc_protocol.py | 152 + python_app/tests/test_kamil_adc_service.py | 276 +- .../tests/test_laser_control_protocol.py | 2 +- .../workflows/kamil_adc_neutral_preprocess.py | 50 +- .../workflows/multi_radar_capture_workflow.py | 24 +- .../workflows/sequential_capture_workflow.py | 24 +- .../run_config_kamil_adc.example.json | 33 +- start.sh | 123 +- 46 files changed, 12097 insertions(+), 1063 deletions(-) create mode 100644 data_acq_and_processing/kamil_adc_collector/README.md create mode 100644 data_acq_and_processing/kamil_adc_collector/include/capture_file_writer.h create mode 100644 data_acq_and_processing/kamil_adc_collector/include/tty_protocol_writer.h create mode 100644 data_acq_and_processing/kamil_adc_collector/src/capture_file_writer.cpp create mode 100644 data_acq_and_processing/kamil_adc_collector/src/main.cpp create mode 100644 data_acq_and_processing/kamil_adc_collector/src/tty_protocol_writer.cpp create mode 100644 data_acq_and_processing/kamil_adc_collector/vendor/lcard/e502api.h create mode 100644 data_acq_and_processing/kamil_adc_collector/vendor/lcard/lcard_pstdint.h create mode 100644 data_acq_and_processing/kamil_adc_collector/vendor/lcard/x502api.h create mode 100644 python_app/hardware_full/kamil_adc/__init__.py create mode 100644 python_app/hardware_full/kamil_adc/laser.py create mode 100644 python_app/hardware_full/kamil_adc/processing.py create mode 100644 python_app/hardware_full/kamil_adc/protocol.py create mode 100644 python_app/hardware_full/kamil_adc/service.py create mode 100644 python_app/hardware_full/kamil_adc/tty_reader.py delete mode 100644 python_app/hardware_full/kamil_adc_service.py create mode 100644 python_app/scripts/kamil_adc_calibrate.py create mode 100644 python_app/tests/test_kamil_adc_processing.py create mode 100644 python_app/tests/test_kamil_adc_protocol.py diff --git a/Makefile b/Makefile index c267e8a..f071be8 100644 --- a/Makefile +++ b/Makefile @@ -60,20 +60,35 @@ PROCESSOR_SOURCES := \ data_acq_and_processing/processing/data_processor/src/main.cpp \ $(LOCATOR_SOURCES) +# Kamil ADC collector: standalone L-Card E-502 acquisition binary. It loads the +# proprietary libx502api/libe502api at runtime via dlopen, so it builds anywhere +# from the vendored headers alone and only needs those libraries present at run +# time. Built on demand (not part of `all`) for radar.model == kamil_adc. +KAMIL_COLLECTOR_DIR := data_acq_and_processing/kamil_adc_collector +KAMIL_COLLECTOR_INCLUDES := -I$(KAMIL_COLLECTOR_DIR)/include -I$(KAMIL_COLLECTOR_DIR)/vendor/lcard +KAMIL_COLLECTOR_LDFLAGS := -pthread -ldl -lutil +KAMIL_COLLECTOR_SOURCES := \ + $(KAMIL_COLLECTOR_DIR)/src/main.cpp \ + $(KAMIL_COLLECTOR_DIR)/src/tty_protocol_writer.cpp \ + $(KAMIL_COLLECTOR_DIR)/src/capture_file_writer.cpp + SWEEP_ORCH_OBJS := $(addprefix $(BUILD_DIR)/,$(COMMON_SOURCES:.cpp=.o) $(ORCH_SOURCES:.cpp=.o)) PREPROCESSOR_OBJS := $(addprefix $(BUILD_DIR)/,$(COMMON_SOURCES:.cpp=.o) $(PREPROC_SOURCES:.cpp=.o)) DATA_PROCESSOR_OBJS := $(addprefix $(BUILD_DIR)/,$(COMMON_SOURCES:.cpp=.o) $(PROCESSOR_SOURCES:.cpp=.o)) -DEPFILES := $(sort $(SWEEP_ORCH_OBJS:.o=.d) $(PREPROCESSOR_OBJS:.o=.d) $(DATA_PROCESSOR_OBJS:.o=.d)) +KAMIL_COLLECTOR_OBJS := $(addprefix $(BUILD_DIR)/,$(KAMIL_COLLECTOR_SOURCES:.cpp=.o)) +DEPFILES := $(sort $(SWEEP_ORCH_OBJS:.o=.d) $(PREPROCESSOR_OBJS:.o=.d) $(DATA_PROCESSOR_OBJS:.o=.d) $(KAMIL_COLLECTOR_OBJS:.o=.d)) TARGETS := \ $(BIN_DIR)/sweep_orchestrator \ $(BIN_DIR)/data_preprocessor \ $(BIN_DIR)/data_processor -.PHONY: all clean +.PHONY: all clean kamil_adc_collector all: $(TARGETS) +kamil_adc_collector: $(BIN_DIR)/kamil_adc_collector + $(BIN_DIR)/sweep_orchestrator: $(SWEEP_ORCH_OBJS) @mkdir -p $(BIN_DIR) $(CXX) $(SWEEP_ORCH_OBJS) -o $@ $(ORCH_LDFLAGS) @@ -86,6 +101,16 @@ $(BIN_DIR)/data_processor: $(DATA_PROCESSOR_OBJS) @mkdir -p $(BIN_DIR) $(CXX) $(DATA_PROCESSOR_OBJS) -o $@ $(LDFLAGS) +# The collector is self-contained: compile its objects with only the vendored +# L-Card headers (no project/VISA includes) by overriding the generic rule's +# variables for these objects, then link with dlopen/openpty support. +$(KAMIL_COLLECTOR_OBJS): INCLUDES := $(KAMIL_COLLECTOR_INCLUDES) +$(KAMIL_COLLECTOR_OBJS): VISA_CXXFLAGS := + +$(BIN_DIR)/kamil_adc_collector: $(KAMIL_COLLECTOR_OBJS) + @mkdir -p $(BIN_DIR) + $(CXX) $(KAMIL_COLLECTOR_OBJS) -o $@ $(KAMIL_COLLECTOR_LDFLAGS) + $(BUILD_DIR)/%.o: %.cpp @mkdir -p $(dir $@) $(CXX) $(CXXFLAGS) $(VISA_CXXFLAGS) $(INCLUDES) -c $< -o $@ diff --git a/README.md b/README.md index e427aef..fe82660 100644 --- a/README.md +++ b/README.md @@ -93,8 +93,9 @@ Run the desktop GUI: ``` `start.sh` supports `--headless` (offscreen, auto-start — for unattended Pi use), -`--kamil-adc`, `--profile PATH`, `--producer-only`, `--skip-build`, and -`--clean-shm`; see `./start.sh --help`. +`--profile PATH`, `--producer-only`, `--skip-build`, and `--clean-shm`; see +`./start.sh --help`. The acquisition device (LibreVNA, Kamil ADC, …) is detected +automatically from the active config's `radar.model` — there is no per-device flag. The web interface is served in-process at `http://:8080`. It mirrors the live plot and offers Start / Stop / Capture controls and the active processor settings. diff --git a/data_acq_and_processing/kamil_adc_collector/README.md b/data_acq_and_processing/kamil_adc_collector/README.md new file mode 100644 index 0000000..835877d --- /dev/null +++ b/data_acq_and_processing/kamil_adc_collector/README.md @@ -0,0 +1,42 @@ +# Kamil ADC collector + +Standalone acquisition binary for the **L-Card E-502** ADC. It captures the +dual-channel main + reference IQ stream and publishes it as 8-byte framed packets +over a PTY, which the Python `kamil_adc` producer +(`python_app/hardware_full/kamil_adc`) reads and processes. + +This is the data *source* for `radar.model == "kamil_adc"`; it is the Kamil ADC +counterpart to the C++ `sweep_orchestrator` used by LibreVNA devices. + +## Layout + +| Path | Contents | +|------|----------| +| `src/` | Collector implementation (`main.cpp`) and the PTY / capture-file writers. | +| `include/` | Headers for the writers. | +| `vendor/lcard/` | L-Card X502/E502 SDK headers (`x502api.h`, `e502api.h`, `lcard_pstdint.h`), vendored verbatim so the collector builds with no external include path. | + +The sources under `src/` and `include/` are imported verbatim from the original +standalone tool and are kept byte-for-byte identical — the wire format and +acquisition behaviour are unchanged; only their home moved into this repo. + +## Build + +```bash +make kamil_adc_collector # -> build/bin/kamil_adc_collector +``` + +It is **not** part of `make all`; `start.sh` builds it automatically when the +active config selects `radar.model == "kamil_adc"`. The build is self-contained: +it needs only the vendored headers and links `-ldl -lutil -lpthread`. + +## Runtime + +The collector loads the proprietary `libx502api.so` / `libe502api.so` at run time +via `dlopen`, so those libraries must be on `LD_LIBRARY_PATH`. The Kamil ADC +service prepends `~/.local/lib` (their default install location) unless the config +pins `LD_LIBRARY_PATH` in `radar.kamil_adc.env`. + +The command-line arguments (capture profile, clocking, the `do8_freq_ref` +reference overlay, etc.) are supplied by `radar.kamil_adc.args` in the run config; +the service appends the generated `tty:` argument. diff --git a/data_acq_and_processing/kamil_adc_collector/include/capture_file_writer.h b/data_acq_and_processing/kamil_adc_collector/include/capture_file_writer.h new file mode 100644 index 0000000..350b69f --- /dev/null +++ b/data_acq_and_processing/kamil_adc_collector/include/capture_file_writer.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include + +struct CapturePacket { + std::size_t packet_index = 0; + std::size_t channel_count = 2; + bool has_di1_trace = false; + std::vector ch1; + std::vector ch2; + std::vector di1; +}; + +class CaptureFileWriter { +public: + CaptureFileWriter(std::string csv_path, + std::string svg_path, + std::string live_html_path, + std::string live_json_path); + + void write(const std::vector& packets, + double frame_freq_hz, + double nominal_range_v) const; + + void initialize_live_plot() const; + void initialize_csv(std::size_t channel_count, bool has_di1_trace) const; + void append_csv_packet(const CapturePacket& packet, + double frame_freq_hz, + std::size_t& global_frame_index) const; + + void update_live_plot(const CapturePacket& packet, + std::size_t packets_seen, + double packets_per_second, + double frame_freq_hz, + const std::string& close_reason, + std::size_t zeroed_samples, + std::size_t stored_samples) const; + + const std::string& live_html_path() const; + const std::string& live_json_path() const; + +private: + void finalize_csv_from_spool(double frame_freq_hz) const; + void write_svg(const std::vector& packets, + double frame_freq_hz, + double nominal_range_v) const; + + std::string csv_path_; + std::string svg_path_; + std::string live_html_path_; + std::string live_json_path_; +}; diff --git a/data_acq_and_processing/kamil_adc_collector/include/tty_protocol_writer.h b/data_acq_and_processing/kamil_adc_collector/include/tty_protocol_writer.h new file mode 100644 index 0000000..c97e92b --- /dev/null +++ b/data_acq_and_processing/kamil_adc_collector/include/tty_protocol_writer.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include + +class TtyProtocolWriter { +public: + struct StatsSnapshot { + std::uint64_t frames_written = 0; + std::uint64_t frames_dropped = 0; + std::uint64_t ring_overflows = 0; + }; + + TtyProtocolWriter(std::string path, std::size_t ring_capacity_bytes); + ~TtyProtocolWriter(); + + TtyProtocolWriter(const TtyProtocolWriter&) = delete; + TtyProtocolWriter& operator=(const TtyProtocolWriter&) = delete; + TtyProtocolWriter(TtyProtocolWriter&& other) noexcept = delete; + TtyProtocolWriter& operator=(TtyProtocolWriter&& other) noexcept = delete; + + void emit_packet_start(uint16_t marker = 0x000A); + 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); + StatsSnapshot stats() const; + + const std::string& path() const; + void throw_if_failed() const; + void shutdown(); + +private: + void enqueue_frame(uint16_t word0, uint16_t word1, uint16_t word2, uint16_t word3); + void worker_loop(); + + std::string path_; + + struct Impl; + std::unique_ptr impl_; +}; diff --git a/data_acq_and_processing/kamil_adc_collector/src/capture_file_writer.cpp b/data_acq_and_processing/kamil_adc_collector/src/capture_file_writer.cpp new file mode 100644 index 0000000..e3dcbf1 --- /dev/null +++ b/data_acq_and_processing/kamil_adc_collector/src/capture_file_writer.cpp @@ -0,0 +1,1019 @@ +#include "capture_file_writer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::size_t kLivePlotMaxColumns = 800; +constexpr uint32_t kCsvSpoolMagic = 0x4C564353U; // "SVCL" +constexpr uint32_t kCsvSpoolVersion = 2U; + +struct CsvSpoolFileHeader { + uint32_t magic = kCsvSpoolMagic; + uint32_t version = kCsvSpoolVersion; + uint32_t channel_count = 0; + uint32_t has_di1_trace = 0; +}; + +struct CsvSpoolPacketHeader { + uint64_t packet_index = 0; + uint64_t channel_count = 0; + uint64_t frame_count = 0; + uint64_t has_di1_trace = 0; +}; + +struct PlotPoint { + std::size_t sample_index = 0; + double value = 0.0; +}; + +std::size_t packet_channel_count(const CapturePacket& packet) { + return (packet.channel_count <= 1U) ? 1U : 2U; +} + +bool packet_has_ch2(const CapturePacket& packet) { + return packet_channel_count(packet) >= 2U; +} + +bool packet_has_di1_trace(const CapturePacket& packet) { + return packet.has_di1_trace && !packet.di1.empty(); +} + +std::size_t packet_frame_count(const CapturePacket& packet) { + std::size_t frames = packet_has_ch2(packet) ? std::min(packet.ch1.size(), packet.ch2.size()) : packet.ch1.size(); + if (packet_has_di1_trace(packet)) { + frames = std::min(frames, packet.di1.size()); + } + return frames; +} + +std::vector build_min_max_trace(const std::vector& data, std::size_t max_columns) { + std::vector result; + if (data.empty()) { + return result; + } + + const std::size_t bucket_size = std::max(1, (data.size() + max_columns - 1) / max_columns); + result.reserve(max_columns * 2); + + for (std::size_t begin = 0; begin < data.size(); begin += bucket_size) { + const std::size_t end = std::min(begin + bucket_size, data.size()); + std::size_t min_index = begin; + std::size_t max_index = begin; + for (std::size_t i = begin + 1; i < end; ++i) { + if (data[i] < data[min_index]) { + min_index = i; + } + if (data[i] > data[max_index]) { + max_index = i; + } + } + + if (min_index <= max_index) { + result.push_back({min_index, data[min_index]}); + if (max_index != min_index) { + result.push_back({max_index, data[max_index]}); + } + } else { + result.push_back({max_index, data[max_index]}); + result.push_back({min_index, data[min_index]}); + } + } + + if (result.back().sample_index != (data.size() - 1)) { + result.push_back({data.size() - 1, data.back()}); + } + + return result; +} + +std::vector build_digital_step_trace(const std::vector& data) { + std::vector result; + if (data.empty()) { + return result; + } + + result.reserve(std::min(data.size() * 2U, 16384U)); + uint8_t previous = data.front(); + result.push_back({0U, static_cast(previous)}); + + for (std::size_t i = 1; i < data.size(); ++i) { + const uint8_t current = data[i]; + if (current != previous) { + result.push_back({i - 1U, static_cast(previous)}); + result.push_back({i, static_cast(current)}); + previous = current; + } + } + + if (result.back().sample_index != (data.size() - 1U)) { + result.push_back({data.size() - 1U, static_cast(data.back())}); + } + + return result; +} + +std::string polyline_points(const std::vector& trace, + std::size_t packet_start_index, + std::size_t total_frames, + double y_min, + double y_max, + double left, + double top, + double width, + double height) { + std::ostringstream out; + out << std::fixed << std::setprecision(3); + const double y_span = std::max(1e-12, y_max - y_min); + const double x_span = (total_frames > 1U) ? static_cast(total_frames - 1U) : 1.0; + + for (const auto& point : trace) { + const double global_index = static_cast(packet_start_index + point.sample_index); + const double x = left + (global_index / x_span) * width; + const double y = top + height - ((point.value - y_min) / y_span) * height; + out << x << "," << y << " "; + } + + return out.str(); +} + +std::string digital_polyline_points(const std::vector& trace, + std::size_t packet_start_index, + std::size_t total_frames, + double left, + double top, + double width, + double height) { + std::ostringstream out; + out << std::fixed << std::setprecision(3); + const double x_span = (total_frames > 1U) ? static_cast(total_frames - 1U) : 1.0; + + for (const auto& point : trace) { + const double global_index = static_cast(packet_start_index + point.sample_index); + const double x = left + (global_index / x_span) * width; + const double y = top + height - ((0.15 + 0.7 * point.value) * height); + out << x << "," << y << " "; + } + + return out.str(); +} + +std::string json_points(const std::vector& trace, double frame_freq_hz) { + std::ostringstream out; + out << std::fixed << std::setprecision(9); + out << "["; + bool first = true; + for (const auto& point : trace) { + if (!first) { + out << ","; + } + first = false; + out << "[" << (static_cast(point.sample_index) / frame_freq_hz) << "," << point.value << "]"; + } + out << "]"; + return out.str(); +} + +[[noreturn]] void fail_write(const std::string& message); + +std::string replace_extension(const std::string& path, const std::string& new_extension) { + const std::size_t slash_pos = path.find_last_of("/\\"); + const std::size_t dot_pos = path.find_last_of('.'); + if ((dot_pos == std::string::npos) || ((slash_pos != std::string::npos) && (dot_pos < slash_pos))) { + return path + new_extension; + } + return path.substr(0, dot_pos) + new_extension; +} + +std::string path_to_script_url(std::string path) { + for (char& ch : path) { + if (ch == '\\') { + ch = '/'; + } + } + + std::string encoded; + encoded.reserve(path.size() + 8); + for (char ch : path) { + switch (ch) { + case ' ': + encoded += "%20"; + break; + case '#': + encoded += "%23"; + break; + case '%': + encoded += "%25"; + break; + case '?': + encoded += "%3F"; + break; + default: + encoded.push_back(ch); + break; + } + } + + if ((encoded.size() >= 2U) && std::isalpha(static_cast(encoded[0])) && (encoded[1] == ':')) { + return "file:///" + encoded; + } + return encoded; +} + +std::string csv_spool_path(const std::string& csv_path) { + return replace_extension(csv_path, ".capture_spool.bin"); +} + +void write_text_file(const std::string& path, const std::string& text) { + std::ofstream file(path, std::ios::binary); + if (!file) { + fail_write("Cannot open file for writing: " + path); + } + file << text; +} + +void write_live_data_script(const std::string& path, const std::string& data_json) { + write_text_file(path, "window.e502LiveData = " + data_json + ";\n"); +} + +void write_live_html_document(const std::string& path, const std::string& data_script_url) { + std::ofstream html(path, std::ios::binary); + if (!html) { + fail_write("Cannot open live HTML for writing: " + path); + } + + html << "\n" + << "\n" + << "\n" + << " \n" + << " \n" + << " E-502 Live Plot\n" + << " \n" + << "\n" + << "\n" + << "
\n" + << "
\n" + << "
\n" + << "
\n" + << "
E-502 Live Packet Plot
\n" + << "
\n" + << " Waiting for packets...\n" + << " \n" + << " \n" + << " \n" + << " \n" + << "
\n" + << "
\n" + << "
\n" + << " Auto-refresh every 500 ms\n" + << "
\n" + << "
\n" + << "
Open this page once and leave it open. It refreshes its data script to pick up new packets.
\n" + << "
\n" + << " \n" + << " \n" + << " \n" + << " \n" + << " \n" + << " \n" + << " Wheel: X zoom, Shift+wheel: Y zoom, double-click: reset\n" + << "
\n" + << " \n" + << "
\n" + << " CH1\n" + << " CH2\n" + << " DI1\n" + << "
\n" + << "
\n" + << "
\n" + << " \n" + << "\n" + << "\n"; +} + +[[noreturn]] void fail_write(const std::string& message) { + throw std::runtime_error(message); +} + +} // namespace + +CaptureFileWriter::CaptureFileWriter(std::string csv_path, + std::string svg_path, + std::string live_html_path, + std::string live_json_path) + : csv_path_(std::move(csv_path)), + svg_path_(std::move(svg_path)), + live_html_path_(std::move(live_html_path)), + live_json_path_(std::move(live_json_path)) {} + +const std::string& CaptureFileWriter::live_html_path() const { + return live_html_path_; +} + +const std::string& CaptureFileWriter::live_json_path() const { + return live_json_path_; +} + +void CaptureFileWriter::write(const std::vector& packets, + double frame_freq_hz, + double nominal_range_v) const { + finalize_csv_from_spool(frame_freq_hz); + write_svg(packets, frame_freq_hz, nominal_range_v); +} + +void CaptureFileWriter::initialize_live_plot() const { + const std::string waiting_json = + "{\n" + " \"status\": \"waiting\",\n" + " \"message\": \"Waiting for first packet...\"\n" + "}\n"; + const std::string live_script_path = replace_extension(live_json_path_, ".js"); + write_text_file(live_json_path_, waiting_json); + write_live_data_script(live_script_path, waiting_json); + write_live_html_document(live_html_path_, path_to_script_url(live_script_path)); +} + +void CaptureFileWriter::initialize_csv(std::size_t channel_count, bool has_di1_trace) const { + std::ofstream csv_file(csv_path_, std::ios::binary | std::ios::trunc); + if (!csv_file) { + fail_write("Cannot prepare CSV output file: " + csv_path_); + } + csv_file << ""; + + const std::string spool_path = csv_spool_path(csv_path_); + std::ofstream spool(spool_path, std::ios::binary | std::ios::trunc); + if (!spool) { + fail_write("Cannot open CSV spool for writing: " + spool_path); + } + const CsvSpoolFileHeader header{ + kCsvSpoolMagic, + kCsvSpoolVersion, + static_cast(channel_count), + static_cast(has_di1_trace ? 1U : 0U) + }; + spool.write(reinterpret_cast(&header), sizeof(header)); + if (!spool) { + fail_write("Cannot initialize CSV spool: " + spool_path); + } +} + +void CaptureFileWriter::append_csv_packet(const CapturePacket& packet, + double frame_freq_hz, + std::size_t& global_frame_index) const { + (void) frame_freq_hz; + (void) global_frame_index; + + const std::string spool_path = csv_spool_path(csv_path_); + std::ofstream spool(spool_path, std::ios::binary | std::ios::app); + if (!spool) { + fail_write("Cannot open CSV spool for appending: " + spool_path); + } + + const std::size_t frames = packet_frame_count(packet); + const std::size_t channel_count = packet_channel_count(packet); + const CsvSpoolPacketHeader header{ + static_cast(packet.packet_index), + static_cast(channel_count), + static_cast(frames), + static_cast(packet_has_di1_trace(packet) ? 1U : 0U) + }; + spool.write(reinterpret_cast(&header), sizeof(header)); + if (frames != 0U) { + spool.write(reinterpret_cast(packet.ch1.data()), + static_cast(frames * sizeof(double))); + if (channel_count >= 2U) { + spool.write(reinterpret_cast(packet.ch2.data()), + static_cast(frames * sizeof(double))); + } + if (packet_has_di1_trace(packet)) { + std::vector di1_bytes(frames); + for (std::size_t i = 0; i < frames; ++i) { + di1_bytes[i] = packet.di1[i] ? 1U : 0U; + } + spool.write(reinterpret_cast(di1_bytes.data()), + static_cast(frames * sizeof(uint8_t))); + } + } + if (!spool) { + fail_write("Cannot append packet to CSV spool: " + spool_path); + } +} + +void CaptureFileWriter::update_live_plot(const CapturePacket& packet, + std::size_t packets_seen, + double packets_per_second, + double frame_freq_hz, + const std::string& close_reason, + std::size_t zeroed_samples, + std::size_t stored_samples) const { + const std::size_t frames = packet_frame_count(packet); + const double duration_ms = (frames == 0U) ? 0.0 : (1000.0 * static_cast(frames) / frame_freq_hz); + const double zeroed_percent = (stored_samples == 0U) + ? 0.0 + : (100.0 * static_cast(zeroed_samples) / static_cast(stored_samples)); + const auto trace1 = build_min_max_trace(packet.ch1, kLivePlotMaxColumns); + const auto trace2 = build_min_max_trace(packet.ch2, kLivePlotMaxColumns); + const auto di1_trace = build_digital_step_trace(packet.di1); + + std::ostringstream json; + json << std::fixed << std::setprecision(9); + json << "{\n" + << " \"status\": \"packet\",\n" + << " \"packetIndex\": " << packet.packet_index << ",\n" + << " \"channelCount\": " << packet_channel_count(packet) << ",\n" + << " \"packetsSeen\": " << packets_seen << ",\n" + << " \"packetsPerSecond\": " << packets_per_second << ",\n" + << " \"framesPerChannel\": " << frames << ",\n" + << " \"frameFreqHz\": " << frame_freq_hz << ",\n" + << " \"durationMs\": " << duration_ms << ",\n" + << " \"closeReason\": \"" << close_reason << "\",\n" + << " \"zeroedSamples\": " << zeroed_samples << ",\n" + << " \"storedSamples\": " << stored_samples << ",\n" + << " \"zeroedPercent\": " << zeroed_percent << ",\n" + << " \"hasDi1Trace\": " << (packet_has_di1_trace(packet) ? "true" : "false") << ",\n" + << " \"di1Frames\": " << packet.di1.size() << ",\n" + << " \"updatedAt\": \"packet " << packet.packet_index << "\",\n" + << " \"ch1\": " << json_points(trace1, frame_freq_hz) << ",\n" + << " \"ch2\": " << json_points(trace2, frame_freq_hz) << ",\n" + << " \"di1\": " << json_points(di1_trace, frame_freq_hz) << "\n" + << "}\n"; + + const std::string json_text = json.str(); + write_text_file(live_json_path_, json_text); + write_live_data_script(replace_extension(live_json_path_, ".js"), json_text); +} + +void CaptureFileWriter::finalize_csv_from_spool(double frame_freq_hz) const { + const std::string spool_path = csv_spool_path(csv_path_); + std::ifstream spool(spool_path, std::ios::binary); + if (!spool) { + fail_write("Cannot open CSV spool for reading: " + spool_path); + } + + CsvSpoolFileHeader file_header{}; + spool.read(reinterpret_cast(&file_header), sizeof(file_header)); + if (!spool || (file_header.magic != kCsvSpoolMagic) || (file_header.version != kCsvSpoolVersion)) { + fail_write("Invalid CSV spool format: " + spool_path); + } + + std::ofstream csv(csv_path_, std::ios::binary | std::ios::trunc); + if (!csv) { + fail_write("Cannot open CSV for final writing: " + csv_path_); + } + + bool header_written = false; + std::size_t global_frame_index = 0; + + while (true) { + CsvSpoolPacketHeader packet_header{}; + spool.read(reinterpret_cast(&packet_header), sizeof(packet_header)); + if (!spool) { + if (spool.eof()) { + break; + } + fail_write("Cannot read packet header from CSV spool: " + spool_path); + } + + const std::size_t packet_index = static_cast(packet_header.packet_index); + const std::size_t channel_count = static_cast(packet_header.channel_count); + const std::size_t frames = static_cast(packet_header.frame_count); + const bool has_di1_trace = packet_header.has_di1_trace != 0U; + + if (!header_written) { + csv << "packet_index,frame_index,global_frame_index,time_s,packet_time_s,ch1_v"; + if (channel_count >= 2U) { + csv << ",ch2_v"; + } + if (has_di1_trace || (file_header.has_di1_trace != 0U)) { + csv << ",di1"; + } + csv << "\n"; + header_written = true; + } + + std::vector ch1(frames); + std::vector ch2((channel_count >= 2U) ? frames : 0U); + std::vector di1(has_di1_trace ? frames : 0U); + if (frames != 0U) { + spool.read(reinterpret_cast(ch1.data()), static_cast(frames * sizeof(double))); + if (!spool) { + fail_write("Cannot read CH1 data from CSV spool: " + spool_path); + } + if (channel_count >= 2U) { + spool.read(reinterpret_cast(ch2.data()), static_cast(frames * sizeof(double))); + if (!spool) { + fail_write("Cannot read CH2 data from CSV spool: " + spool_path); + } + } + if (has_di1_trace) { + spool.read(reinterpret_cast(di1.data()), static_cast(frames * sizeof(uint8_t))); + if (!spool) { + fail_write("Cannot read DI1 trace from CSV spool: " + spool_path); + } + } + } + + std::ostringstream out; + out << std::fixed << std::setprecision(9); + for (std::size_t i = 0; i < frames; ++i) { + const double time_s = static_cast(global_frame_index) / frame_freq_hz; + const double packet_time_s = static_cast(i) / frame_freq_hz; + out << packet_index << "," << i << "," << global_frame_index << "," + << time_s << "," << packet_time_s << "," << ch1[i]; + if (channel_count >= 2U) { + out << "," << ch2[i]; + } + if (has_di1_trace || (file_header.has_di1_trace != 0U)) { + out << "," << (has_di1_trace ? static_cast(di1[i]) : 0U); + } + out << "\n"; + ++global_frame_index; + } + csv << out.str(); + } + + if (!header_written) { + csv << "packet_index,frame_index,global_frame_index,time_s,packet_time_s,ch1_v"; + if (file_header.channel_count >= 2U) { + csv << ",ch2_v"; + } + if (file_header.has_di1_trace != 0U) { + csv << ",di1"; + } + csv << "\n"; + } + + csv.flush(); + std::remove(spool_path.c_str()); +} + +void CaptureFileWriter::write_svg(const std::vector& packets, + double frame_freq_hz, + double nominal_range_v) const { + std::ofstream file(svg_path_, std::ios::binary); + if (!file) { + fail_write("Cannot open SVG for writing: " + svg_path_); + } + + std::size_t total_frames = 0; + double min_y = std::numeric_limits::infinity(); + double max_y = -std::numeric_limits::infinity(); + const std::size_t channel_count = packets.empty() ? 2U : packet_channel_count(packets.front()); + const bool has_di1_trace = std::any_of(packets.begin(), packets.end(), [](const CapturePacket& packet) { + return packet_has_di1_trace(packet); + }); + for (const auto& packet : packets) { + const std::size_t frames = packet_frame_count(packet); + total_frames += frames; + for (std::size_t i = 0; i < frames; ++i) { + min_y = std::min(min_y, packet.ch1[i]); + max_y = std::max(max_y, packet.ch1[i]); + if (packet_has_ch2(packet)) { + min_y = std::min(min_y, packet.ch2[i]); + max_y = std::max(max_y, packet.ch2[i]); + } + } + } + + if (!std::isfinite(min_y) || !std::isfinite(max_y) || (min_y == max_y)) { + min_y = -nominal_range_v; + max_y = nominal_range_v; + } else { + const double pad = std::max(0.1, (max_y - min_y) * 0.08); + min_y -= pad; + max_y += pad; + } + + const double total_time_s = (total_frames > 1U) ? (static_cast(total_frames - 1U) / frame_freq_hz) : 0.0; + const double width = 1400.0; + const double height = 800.0; + const double left = 90.0; + const double right = 40.0; + const double top = 40.0; + const double bottom = 80.0; + const double plot_w = width - left - right; + const double plot_h = height - top - bottom; + const double zero_y = top + plot_h - ((0.0 - min_y) / std::max(1e-12, max_y - min_y)) * plot_h; + const double di1_band_top = top + 10.0; + const double di1_band_height = 44.0; + + file << "\n"; + file << " \n"; + file << " \n"; + + for (int i = 0; i <= 10; ++i) { + const double x = left + (plot_w * i / 10.0); + const double y = top + (plot_h * i / 10.0); + file << " \n"; + file << " \n"; + } + + if ((0.0 >= min_y) && (0.0 <= max_y)) { + file << " \n"; + } + if (has_di1_trace) { + file << " \n"; + file << " \n"; + file << " DI1\n"; + } + + std::size_t frame_offset = 0; + for (std::size_t packet_idx = 0; packet_idx < packets.size(); ++packet_idx) { + const auto& packet = packets[packet_idx]; + const std::size_t frames = packet_frame_count(packet); + if (frames == 0U) { + continue; + } + + const double x0 = left + ((total_frames > 1U) + ? (static_cast(frame_offset) / static_cast(total_frames - 1U)) * plot_w + : 0.0); + const double x1 = left + ((total_frames > 1U) + ? (static_cast(frame_offset + frames - 1U) / static_cast(total_frames - 1U)) * plot_w + : plot_w); + const char* packet_fill = (packet_idx % 2U == 0U) ? "#f7fafc" : "#fdfefe"; + file << " \n"; + if (packet_idx != 0U) { + file << " \n"; + } + + const auto trace1 = build_min_max_trace(packet.ch1, 1200); + const auto trace2 = build_min_max_trace(packet.ch2, 1200); + const auto trace_di1 = build_digital_step_trace(packet.di1); + file << " \n"; + if (packet_has_ch2(packet)) { + file << " \n"; + } + if (packet_has_di1_trace(packet)) { + file << " \n"; + } + + if (packets.size() <= 24U) { + file << " P" + << packet.packet_index << "\n"; + } + + frame_offset += frames; + } + + file << " E-502 capture: " << packets.size() << " packet(s), " + << ((channel_count >= 2U) ? "CH1 and CH2" : "CH1 only"); + if (has_di1_trace) { + file << ", DI1 trace"; + } + file << "\n"; + file << " time, s\n"; + file << " V\n"; + + file << std::fixed << std::setprecision(6); + for (int i = 0; i <= 10; ++i) { + const double x = left + (plot_w * i / 10.0); + const double t = total_time_s * i / 10.0; + file << " " << t << "\n"; + + const double y = top + plot_h - (plot_h * i / 10.0); + const double v = min_y + (max_y - min_y) * i / 10.0; + file << " " << v << "\n"; + } + + const double legend_y = height - 48.0; + file << " \n"; + file << " CH1\n"; + if (channel_count >= 2U) { + file << " \n"; + file << " CH2\n"; + } + if (has_di1_trace) { + file << " \n"; + file << " DI1\n"; + } + file << "\n"; +} diff --git a/data_acq_and_processing/kamil_adc_collector/src/main.cpp b/data_acq_and_processing/kamil_adc_collector/src/main.cpp new file mode 100644 index 0000000..0eb7cb5 --- /dev/null +++ b/data_acq_and_processing/kamil_adc_collector/src/main.cpp @@ -0,0 +1,3263 @@ +#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 "capture_file_writer.h" +#include "tty_protocol_writer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 + #include + #include +#endif + +namespace { + +enum class StopMode { + TargetFrames, + DiSyn2Rise, + DiSyn2Fall +}; + +enum class Di1Mode { + ZeroOnChange, + Trace, + Ignore +}; + +enum class CaptureProfile { + Manual, + Phase, + Amplitude +}; + +struct Config { + std::string serial; + std::optional ip_addr; + + CaptureProfile profile = CaptureProfile::Manual; + uint32_t channel_count = 2; + uint32_t mode = X502_LCH_MODE_DIFF; + uint32_t range = X502_ADC_RANGE_02; + uint32_t ch1 = 2; + uint32_t ch2 = 3; + + double sample_clock_hz = 125000.0; + bool sample_clock_specified = false; + bool max_internal_clock = false; + double duration_ms = 100.0; + uint32_t packet_limit = 0; + uint32_t internal_ref_freq = X502_REF_FREQ_2000KHZ; + + uint32_t sync_mode = X502_SYNC_DI_SYN1_RISE; + uint32_t sync_start_mode = X502_SYNC_DI_SYN2_RISE; + StopMode stop_mode = StopMode::DiSyn2Fall; + Di1Mode di1_mode = Di1Mode::ZeroOnChange; + + uint32_t recv_block_words = 32768; + uint32_t recv_timeout_ms = 50; + uint32_t stats_period_ms = 1000; + uint32_t start_wait_ms = 10000; + uint32_t input_buffer_words = 8 * 1024 * 1024; + uint32_t input_step_words = 32768; + uint32_t live_update_period_ms = 1000; + uint32_t svg_history_packets = 50; + + bool recv_block_specified = false; + bool input_buffer_specified = false; + bool input_step_specified = false; + bool live_update_specified = false; + bool mode_specified = false; + bool channel_count_specified = false; + bool ch1_specified = false; + bool ch2_specified = false; + + bool pullup_syn1 = false; + bool pullup_syn2 = false; + bool pulldown_conv_in = false; + bool pulldown_start_in = false; + + std::string csv_path = "capture.csv"; + std::string svg_path = "capture.svg"; + std::string live_html_path = "live_plot.html"; + std::string live_json_path = "live_plot.json"; + std::optional tty_path; + bool di1_group_average = false; + bool do1_toggle_per_frame = false; + bool do1_noise_subtract = false; + bool do1_raw_tty_marked = false; + bool do1_pair_subtract_avg = false; + std::optional noise_avg_steps; + bool do8_freq_ref = false; + uint32_t do8_cycle_period_ticks = 10; + std::optional do8_amplitude_threshold; +}; + +[[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::max())) { + fail("Invalid integer for " + field_name + ": " + text); + } + return static_cast(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 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] > 255) { + 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"); + } +} + +uint32_t parse_mode(const std::string& text) { + const std::string value = trim_copy(text); + if ((value == "comm") || (value == "gnd") || (value == "single_ended")) { + return X502_LCH_MODE_COMM; + } + if ((value == "diff") || (value == "differential")) { + return X502_LCH_MODE_DIFF; + } + fail("Unsupported input mode: " + text); +} + +CaptureProfile parse_profile(const std::string& text) { + const std::string value = trim_copy(text); + if (value == "phase") { + return CaptureProfile::Phase; + } + if (value == "amplitude") { + return CaptureProfile::Amplitude; + } + fail("Unsupported profile: " + text + ". Use phase or amplitude"); +} + +std::string profile_to_string(CaptureProfile profile) { + switch (profile) { + case CaptureProfile::Manual: + return "manual"; + case CaptureProfile::Phase: + return "phase"; + case CaptureProfile::Amplitude: + return "amplitude"; + default: + return "unknown"; + } +} + +struct ProfileSettings { + uint32_t channel_count = 2; + uint32_t mode = X502_LCH_MODE_DIFF; + uint32_t ch1 = 2; + uint32_t ch2 = 3; +}; + +ProfileSettings profile_settings(CaptureProfile profile) { + switch (profile) { + case CaptureProfile::Phase: + return {2U, X502_LCH_MODE_DIFF, 2U, 3U}; + case CaptureProfile::Amplitude: + return {1U, X502_LCH_MODE_COMM, 0U, 0U}; + case CaptureProfile::Manual: + default: + fail("Internal error: no settings for manual profile"); + } +} + +void apply_profile(Config& cfg) { + if (cfg.profile == CaptureProfile::Manual) { + return; + } + + const ProfileSettings settings = profile_settings(cfg.profile); + const std::string profile_name = profile_to_string(cfg.profile); + const std::string profile_arg = "profile:" + profile_name; + + if (cfg.mode_specified && (cfg.mode != settings.mode)) { + fail(profile_arg + " conflicts with explicit mode"); + } + if (cfg.channel_count_specified && (cfg.channel_count != settings.channel_count)) { + fail(profile_arg + " conflicts with explicit channels"); + } + if (cfg.ch1_specified && (cfg.ch1 != settings.ch1)) { + fail(profile_arg + " conflicts with explicit ch1"); + } + if (settings.channel_count <= 1U) { + if (cfg.ch2_specified) { + fail(profile_arg + " does not allow ch2"); + } + } else if (cfg.ch2_specified && (cfg.ch2 != settings.ch2)) { + fail(profile_arg + " conflicts with explicit ch2"); + } + + cfg.mode = settings.mode; + cfg.channel_count = settings.channel_count; + cfg.ch1 = settings.ch1; + cfg.ch2 = settings.ch2; +} + +uint32_t parse_internal_ref_freq(const std::string& text) { + const std::string value = trim_copy(text); + if ((value == "2000000") || (value == "2000khz") || (value == "2mhz")) { + return X502_REF_FREQ_2000KHZ; + } + if ((value == "1500000") || (value == "1500khz") || (value == "1.5mhz")) { + return X502_REF_FREQ_1500KHZ; + } + fail("Unsupported internal_ref_hz value: " + text + ". Use 1500000 or 2000000"); +} + +uint32_t parse_channel_count(const std::string& text) { + const uint32_t value = parse_u32(text, "channels"); + if ((value == 1U) || (value == 2U)) { + return value; + } + fail("channels must be 1 or 2"); +} + +std::string ref_freq_to_string(uint32_t freq) { + switch (freq) { + case X502_REF_FREQ_2000KHZ: + return "2000000"; + case X502_REF_FREQ_1500KHZ: + return "1500000"; + default: + return "unknown"; + } +} + +bool use_internal_max_clock(const Config& cfg) { + return (cfg.sync_mode == X502_SYNC_INTERNAL) && (cfg.max_internal_clock || !cfg.sample_clock_specified); +} + +uint32_t parse_sync_mode(const std::string& text) { + const std::string value = trim_copy(text); + if ((value == "conv_in") || (value == "start_in") || (value == "external_master")) { + return X502_SYNC_EXTERNAL_MASTER; + } + if (value == "di_syn1_rise") { + return X502_SYNC_DI_SYN1_RISE; + } + if (value == "di_syn1_fall") { + return X502_SYNC_DI_SYN1_FALL; + } + if (value == "di_syn2_rise") { + return X502_SYNC_DI_SYN2_RISE; + } + if (value == "di_syn2_fall") { + return X502_SYNC_DI_SYN2_FALL; + } + if ((value == "internal") || (value == "immediate")) { + return X502_SYNC_INTERNAL; + } + fail("Unsupported sync mode: " + text); +} + +std::string sync_mode_to_string(uint32_t mode, bool for_start) { + switch (mode) { + case X502_SYNC_INTERNAL: + return for_start ? "immediate" : "internal"; + case X502_SYNC_EXTERNAL_MASTER: + return for_start ? "start_in" : "conv_in"; + case X502_SYNC_DI_SYN1_RISE: + return "di_syn1_rise"; + case X502_SYNC_DI_SYN1_FALL: + return "di_syn1_fall"; + case X502_SYNC_DI_SYN2_RISE: + return "di_syn2_rise"; + case X502_SYNC_DI_SYN2_FALL: + return "di_syn2_fall"; + default: + return "unknown"; + } +} + +StopMode parse_stop_mode(const std::string& text) { + const std::string value = trim_copy(text); + if ((value == "frames") || (value == "duration") || (value == "none")) { + return StopMode::TargetFrames; + } + if (value == "di_syn2_rise") { + return StopMode::DiSyn2Rise; + } + if (value == "di_syn2_fall") { + return StopMode::DiSyn2Fall; + } + fail("Unsupported stop mode: " + text); +} + +std::string stop_mode_to_string(StopMode mode) { + switch (mode) { + case StopMode::TargetFrames: + return "target_frames"; + case StopMode::DiSyn2Rise: + return "di_syn2_rise"; + case StopMode::DiSyn2Fall: + return "di_syn2_fall"; + default: + return "unknown"; + } +} + +Di1Mode parse_di1_mode(const std::string& text) { + const std::string value = trim_copy(text); + if ((value == "zero") || (value == "zero_on_change") || (value == "mark")) { + return Di1Mode::ZeroOnChange; + } + if ((value == "trace") || (value == "plot") || (value == "stream")) { + return Di1Mode::Trace; + } + if ((value == "ignore") || (value == "off") || (value == "none")) { + return Di1Mode::Ignore; + } + fail("Unsupported di1 mode: " + text); +} + +std::string di1_mode_to_string(Di1Mode mode) { + switch (mode) { + case Di1Mode::ZeroOnChange: + return "zero"; + case Di1Mode::Trace: + return "trace"; + case Di1Mode::Ignore: + return "ignore"; + default: + return "unknown"; + } +} + +bool sync_uses_di_syn1(uint32_t mode) { + return (mode == X502_SYNC_DI_SYN1_RISE) || (mode == X502_SYNC_DI_SYN1_FALL); +} + +bool sync_uses_di_syn2(uint32_t mode) { + return (mode == X502_SYNC_DI_SYN2_RISE) || (mode == X502_SYNC_DI_SYN2_FALL); +} + +std::string phy_channel_name(uint32_t mode, uint32_t phy_ch) { + if (mode == X502_LCH_MODE_DIFF) { + return "X" + std::to_string(phy_ch + 1) + "-Y" + std::to_string(phy_ch + 1); + } + if (phy_ch < 16) { + return "X" + std::to_string(phy_ch + 1); + } + if (phy_ch < 32) { + return "Y" + std::to_string((phy_ch - 16) + 1); + } + return "CH" + std::to_string(phy_ch); +} + +void print_help(const char* exe_name) { + std::cout + << "Usage:\n" + << " " << exe_name << " [serial:SN] [ip:192.168.0.10] [profile:phase|amplitude]\n" + << " [channels:2] [ch1:2] [ch2:3]\n" + << " [mode:diff|comm] [range:0.2] [clock:di_syn1_rise]\n" + << " [start:di_syn2_rise] [stop:di_syn2_fall] [sample_clock_hz:125000|max]\n" + << " [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:/tmp/ttyADC_data] [di1_group_avg]\n" + << " [do1_toggle_per_frame] [do1_noise_subtract] [do1_raw_tty_marked] [do1_pair_subtract_avg] [noise_avg_steps:N]\n" + << " [do8_freq_ref] [do8_cycle_period:10] [do8_threshold:X]\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" + << "\n" + << "Defaults for E-502:\n" + << " channels:2 -> capture CH1 and CH2\n" + << " ch1:2, ch2:3 -> X3-Y3 and X4-Y4\n" + << " mode:diff -> differential measurement\n" + << " range:0.2 -> +/-0.2 V range\n" + << " supported ranges -> 10, 5, 2, 1, 0.5, 0.2 V\n" + << " channels:1 -> capture only CH1, CH2 is ignored\n" + << " clock:di_syn1_rise-> external sample clock on DI_SYN1 rising edge\n" + << " clock:internal -> module generates its own clock\n" + << " start:di_syn2_rise-> packet starts on DI_SYN2 rising edge\n" + << " stop:di_syn2_fall -> packet stops on DI_SYN2 falling edge\n" + << " sample_clock_hz:125000 -> requested ADC sample rate; for external clock it is the expected rate\n" + << " sample_clock_hz:max -> with clock:internal, use the maximum ADC speed\n" + << " internal_ref_hz:2000000 -> internal base clock for clock:internal (1500000 or 2000000)\n" + << " di1:zero -> write ADC sample as 0 on each DI1 level change\n" + << " di1:trace -> keep ADC unchanged and store DI1 as a separate synchronous trace\n" + << " di1:ignore -> ignore DI1 for both zeroing and plotting\n" + << " stats_period_ms:1000 -> print online transfer/capture statistics every 1000 ms (0 disables)\n" + << " recv_block:32768 -> request up to 32768 raw 32-bit words per X502_Recv() call\n" + << " live_update_period_ms:1000 -> refresh live HTML/JSON no more than once per second" + << " (ignored in tty fast stream-only modes)\n" + << " svg_history_packets:50 -> keep last 50 packets in RAM for final SVG (0 keeps all, risky)" + << " (ignored in tty fast stream-only modes)\n" + << " duration_ms:100 -> max packet length if stop edge does not arrive\n" + << " packet_limit:0 -> 0 means continuous until Ctrl+C, N means stop after N packets\n" + << " 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 outside tty fast stream-only modes\n" + << " tty:/tmp/ttyADC_data -> write a continuous legacy 4-word CH1/CH2 stream; with channels:1, CH2 is 0\n" + << " di1_group_avg -> with tty + di1:trace, emit one averaged 4-word step per constant DI1 run\n" + << " do1_toggle_per_frame -> hardware cyclic DO1 pattern in module memory:\n" + << " DO1 outputs 00110011... continuously (toggle every 2 ADC ticks)\n" + << " without per-tick DOUT updates from PC\n" + << " do1_noise_subtract -> with tty + do1_toggle_per_frame, use DI1 state from synchronous DIN\n" + << " (expected DO1->DI1 loopback) to take previous DI1=HIGH step as baseline and subtract it\n" + << " from useful steps before tty output\n" + << " (only corrected DO1=LOW steps are sent to tty)\n" + << " do1_raw_tty_marked -> with tty + do1_toggle_per_frame + phase profile, emit raw per-step averages\n" + << " from both phase channels, tagged by DI1 level from synchronous DIN:\n" + << " word0=0x00A3 for DO1/DI1 LOW, word0=0x00A4 for DO1/DI1 HIGH\n" + << " no baseline subtraction is applied in this mode\n" + << " do1_pair_subtract_avg -> with tty + do1_toggle_per_frame + phase profile, pair adjacent CH1/CH2 ticks,\n" + << " drop boundary pairs with mismatched DI1/DI2, compute low-high by DI2 per pair,\n" + << " average within each constant DI1 run, and emit legacy 4-word tty frames (word0=0x000A)\n" + << " noise_avg_steps:N -> kept for compatibility in do1_noise_subtract mode (current baseline is previous HIGH step)\n" + << " (still required when do1_noise_subtract is enabled)\n" + << " do8_freq_ref -> enable DO8/DI8 frequency reference overlay (DO8 and DI8 must be physically looped back);\n" + << " DO8 toggles HIGH for 2 ticks within each do8_cycle_period cycle;\n" + << " DI8=HIGH samples are accumulated separately and emitted as 0x00A8 frames\n" + << " do8_cycle_period:10 -> DO8 cycle period in ADC ticks (default 10, minimum 4)\n" + << " do8_threshold:X -> only emit DI8 reference frame when channel amplitude exceeds X (raw ADC code units)\n" + << " tty fast stream-only modes -> di1_group_avg, do1_noise_subtract, do1_raw_tty_marked, do1_pair_subtract_avg;\n" + << " skip CSV/SVG/live outputs\n" + << " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n" + << "\n" + << "Profiles:\n" + << " profile:phase -> mode:diff channels:2 ch1:2 ch2:3 (X3-Y3 and X4-Y4)\n" + << " profile:amplitude -> mode:comm channels:1 ch1:0 (X1, raw AD8317 voltage)\n" + << " profile:* is strict -> conflicting mode/channels/ch1/ch2 arguments are rejected\n" + << "\n" + << "Differential physical channel mapping:\n" + << " 0..15 -> X1-Y1 .. X16-Y16\n" + << "\n" + << "Common-ground physical channel mapping:\n" + << " 0..15 -> X1..X16\n" + << " 16..31 -> Y1..Y16\n" + << "\n" + << "Useful sync lines on E-502:\n" + << " clock: conv_in | di_syn1_rise | di_syn1_fall | di_syn2_rise | di_syn2_fall\n" + << " start: immediate | start_in | di_syn1_rise | di_syn1_fall | di_syn2_rise | di_syn2_fall\n" + << " stop: frames | di_syn2_rise | di_syn2_fall\n" + << "\n" + << "This build enables synchronous DIN together with ADC. DI_SYN2 stop edges are detected\n" + << "inside the same input stream, packets are split continuously by DI_SYN2 edges, and DI1\n" + << "can either zero ADC samples on change, be exported as a separate synchronous trace, or be ignored.\n" + << "Outside tty fast stream-only modes, open live_plot.html in a browser to see the live graph update over time.\n" + << "The live HTML supports X/Y zoom buttons, mouse-wheel zoom, and reset.\n" + << "For tty output, use di1_group_avg together with di1:trace to emit one averaged 4-word step\n" + << "per constant DI1 run while keeping the current ring-buffered binary frame format.\n" + << "For DO1 subtraction with loopback, use do1_toggle_per_frame + do1_noise_subtract + noise_avg_steps:N;\n" + << "DI1 from synchronous DIN is used as the actual DO1 state marker, and each LOW step subtracts previous HIGH step.\n" + << "For raw loopback diagnostics, use do1_toggle_per_frame + do1_raw_tty_marked in phase profile;\n" + << "steps are tagged in word0 as 0x00A3 (LOW) / 0x00A4 (HIGH) with raw per-step channel averages.\n" + << "For DI1-stable low/high DI2 subtraction with legacy tty output, use do1_toggle_per_frame + do1_pair_subtract_avg\n" + << "in phase profile; boundary CH1/CH2 pairs with mismatched DI1/DI2 are dropped before averaging.\n" + << "Amplitude mode keeps the raw AD8317 voltage as captured on X1; no inversion is applied.\n" + << "\n" + << "Phase profile example:\n" + << " " << exe_name + << " profile:phase clock:di_syn1_rise start:di_syn2_rise stop:di_syn2_fall sample_clock_hz:125000" + << " duration_ms:100 packet_limit:10 csv:chirp.csv svg:chirp.svg\n" + << "\n" + << "Amplitude profile example:\n" + << " " << exe_name + << " profile:amplitude clock:internal internal_ref_hz:2000000 start:di_syn2_rise stop:di_syn2_fall" + << " sample_clock_hz:max duration_ms:100 packet_limit:0 csv:amplitude.csv svg:amplitude.svg\n" + << "\n" + << "Amplitude TTY DI1-group average example:\n" + << " " << exe_name + << " profile:amplitude clock:internal internal_ref_hz:2000000 start:di_syn2_rise stop:di_syn2_fall" + << " sample_clock_hz:max range:0.2 di1:trace di1_group_avg duration_ms:100 packet_limit:0" + << " tty:/tmp/ttyADC_data\n" + << "\n" + << "Amplitude TTY DO1 noise subtract example:\n" + << " " << exe_name + << " profile:amplitude clock:internal internal_ref_hz:2000000 start:di_syn2_rise stop:di_syn2_fall" + << " sample_clock_hz:max range:0.2 do1_toggle_per_frame do1_noise_subtract noise_avg_steps:16" + << " duration_ms:100 packet_limit:0 tty:/tmp/ttyADC_data\n" + << "\n" + << "Phase TTY raw DO1-level tagged example:\n" + << " " << exe_name + << " profile:phase clock:internal internal_ref_hz:2000000 start:di_syn2_rise stop:di_syn2_fall" + << " sample_clock_hz:max do1_toggle_per_frame do1_raw_tty_marked" + << " duration_ms:100 packet_limit:0 tty:/tmp/ttyADC_data\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.pullup_syn1 = true; + continue; + } + if (arg == "pullup_syn2") { + cfg.pullup_syn2 = true; + continue; + } + if (arg == "pulldown_conv_in") { + cfg.pulldown_conv_in = true; + continue; + } + if (arg == "pulldown_start_in") { + cfg.pulldown_start_in = 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, "profile:")) { + cfg.profile = parse_profile(arg.substr(8)); + continue; + } + if (starts_with(arg, "mode:")) { + cfg.mode = parse_mode(arg.substr(5)); + cfg.mode_specified = true; + continue; + } + if (starts_with(arg, "channels:")) { + cfg.channel_count = parse_channel_count(arg.substr(9)); + cfg.channel_count_specified = true; + continue; + } + if (starts_with(arg, "range:")) { + cfg.range = parse_range(arg.substr(6)); + continue; + } + if (starts_with(arg, "ch1:")) { + cfg.ch1 = parse_u32(arg.substr(4), "ch1"); + cfg.ch1_specified = true; + continue; + } + if (starts_with(arg, "ch2:")) { + cfg.ch2 = parse_u32(arg.substr(4), "ch2"); + cfg.ch2_specified = true; + continue; + } + if (starts_with(arg, "clock:")) { + cfg.sync_mode = parse_sync_mode(arg.substr(6)); + continue; + } + if (starts_with(arg, "start:")) { + cfg.sync_start_mode = parse_sync_mode(arg.substr(6)); + continue; + } + if (starts_with(arg, "stop:")) { + cfg.stop_mode = parse_stop_mode(arg.substr(5)); + continue; + } + if (starts_with(arg, "di1:")) { + cfg.di1_mode = parse_di1_mode(arg.substr(4)); + continue; + } + if ((arg == "di1_group_avg") || (arg == "di1_avg")) { + cfg.di1_group_average = true; + continue; + } + if (arg == "do1_noise_subtract") { + cfg.do1_noise_subtract = true; + continue; + } + if (arg == "do1_raw_tty_marked") { + cfg.do1_raw_tty_marked = true; + continue; + } + if (arg == "do1_pair_subtract_avg") { + cfg.do1_pair_subtract_avg = true; + continue; + } + if (arg == "do1_toggle_per_frame") { + cfg.do1_toggle_per_frame = true; + continue; + } + if (starts_with(arg, "noise_avg_steps:")) { + cfg.noise_avg_steps = parse_u32(arg.substr(16), "noise_avg_steps"); + continue; + } + if (arg == "do8_freq_ref") { + cfg.do8_freq_ref = true; + continue; + } + if (starts_with(arg, "do8_cycle_period:")) { + cfg.do8_cycle_period_ticks = parse_u32(arg.substr(17), "do8_cycle_period"); + continue; + } + if (starts_with(arg, "do8_threshold:")) { + cfg.do8_amplitude_threshold = parse_double(arg.substr(14), "do8_threshold"); + continue; + } + if (starts_with(arg, "sample_clock_hz:")) { + const std::string value = trim_copy(arg.substr(16)); + cfg.sample_clock_specified = true; + if ((value == "max") || (value == "maximum")) { + cfg.max_internal_clock = true; + } else { + cfg.sample_clock_hz = parse_double(value, "sample_clock_hz"); + cfg.max_internal_clock = false; + } + continue; + } + if (starts_with(arg, "internal_ref_hz:")) { + cfg.internal_ref_freq = parse_internal_ref_freq(arg.substr(16)); + continue; + } + if (starts_with(arg, "duration_ms:")) { + cfg.duration_ms = parse_double(arg.substr(12), "duration_ms"); + continue; + } + if (starts_with(arg, "packet_limit:")) { + cfg.packet_limit = parse_u32(arg.substr(13), "packet_limit"); + continue; + } + if (starts_with(arg, "recv_block:")) { + cfg.recv_block_words = parse_u32(arg.substr(11), "recv_block"); + cfg.recv_block_specified = true; + 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, "stats_period_ms:")) { + cfg.stats_period_ms = parse_u32(arg.substr(16), "stats_period_ms"); + continue; + } + if (starts_with(arg, "live_update_period_ms:")) { + cfg.live_update_period_ms = parse_u32(arg.substr(22), "live_update_period_ms"); + cfg.live_update_specified = true; + continue; + } + if (starts_with(arg, "svg_history_packets:")) { + cfg.svg_history_packets = parse_u32(arg.substr(20), "svg_history_packets"); + continue; + } + if (starts_with(arg, "start_wait_ms:")) { + cfg.start_wait_ms = parse_u32(arg.substr(14), "start_wait_ms"); + continue; + } + if (starts_with(arg, "buffer_words:")) { + cfg.input_buffer_words = parse_u32(arg.substr(13), "buffer_words"); + cfg.input_buffer_specified = true; + continue; + } + if (starts_with(arg, "step_words:")) { + cfg.input_step_words = parse_u32(arg.substr(11), "step_words"); + cfg.input_step_specified = true; + continue; + } + if (starts_with(arg, "csv:")) { + cfg.csv_path = arg.substr(4); + continue; + } + if (starts_with(arg, "svg:")) { + cfg.svg_path = arg.substr(4); + continue; + } + if (starts_with(arg, "live_html:")) { + cfg.live_html_path = arg.substr(10); + continue; + } + if (starts_with(arg, "live_json:")) { + cfg.live_json_path = arg.substr(10); + continue; + } + if (starts_with(arg, "tty:")) { + cfg.tty_path = arg.substr(4); + continue; + } + fail("Unknown argument: " + arg); + } + + apply_profile(cfg); + + if (cfg.duration_ms <= 0.0) { + fail("duration_ms must be > 0"); + } + if (!cfg.max_internal_clock && (cfg.sample_clock_hz <= 0.0)) { + fail("sample_clock_hz must be > 0"); + } + if (cfg.max_internal_clock && (cfg.sync_mode != X502_SYNC_INTERNAL)) { + fail("sample_clock_hz:max is only valid together with clock:internal"); + } + const bool high_rate_capture = + use_internal_max_clock(cfg) || + ((cfg.sample_clock_hz >= 1000000.0) && + ((cfg.sync_mode == X502_SYNC_INTERNAL) || cfg.sample_clock_specified)); + if (high_rate_capture) { + if (!cfg.recv_block_specified) { + cfg.recv_block_words = std::max(cfg.recv_block_words, 32768U); + } + if (!cfg.input_step_specified) { + cfg.input_step_words = std::max(cfg.input_step_words, 32768U); + } + if (!cfg.input_buffer_specified) { + cfg.input_buffer_words = std::max(cfg.input_buffer_words, 8U * 1024U * 1024U); + } + if (!cfg.live_update_specified) { + cfg.live_update_period_ms = std::max(cfg.live_update_period_ms, 1000U); + } + } + if (cfg.tty_path && (cfg.di1_group_average || cfg.do1_noise_subtract || cfg.do1_raw_tty_marked || cfg.do1_pair_subtract_avg)) { + if (!cfg.recv_block_specified) { + cfg.recv_block_words = std::max(cfg.recv_block_words, 65536U); + } + if (!cfg.input_step_specified) { + cfg.input_step_words = std::max(cfg.input_step_words, 65536U); + } + if (!cfg.input_buffer_specified) { + cfg.input_buffer_words = std::max(cfg.input_buffer_words, 16U * 1024U * 1024U); + } + } + if (cfg.recv_block_words == 0) { + fail("recv_block must be > 0"); + } + if (cfg.input_step_words == 0) { + 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; + } + if (cfg.di1_group_average && (cfg.di1_mode != Di1Mode::Trace)) { + fail("di1_group_avg requires di1:trace"); + } + if (cfg.di1_group_average && cfg.do1_raw_tty_marked) { + fail("do1_raw_tty_marked is incompatible with di1_group_avg"); + } + if (cfg.di1_group_average && cfg.do1_pair_subtract_avg) { + fail("do1_pair_subtract_avg is incompatible with di1_group_avg"); + } + if (cfg.do1_noise_subtract) { + if (!cfg.tty_path.has_value()) { + fail("do1_noise_subtract requires tty:"); + } + if (!cfg.do1_toggle_per_frame) { + fail("do1_noise_subtract requires do1_toggle_per_frame"); + } + if (cfg.di1_group_average) { + fail("do1_noise_subtract is incompatible with di1_group_avg"); + } + if (cfg.do1_raw_tty_marked) { + fail("do1_noise_subtract is incompatible with do1_raw_tty_marked"); + } + if (cfg.do1_pair_subtract_avg) { + fail("do1_noise_subtract is incompatible with do1_pair_subtract_avg"); + } + if (!cfg.noise_avg_steps.has_value()) { + fail("do1_noise_subtract requires noise_avg_steps:N"); + } + if (*cfg.noise_avg_steps == 0U) { + fail("noise_avg_steps must be > 0"); + } + } else if (cfg.noise_avg_steps.has_value()) { + fail("noise_avg_steps:N can only be used together with do1_noise_subtract"); + } + if (cfg.do1_raw_tty_marked) { + if (!cfg.tty_path.has_value()) { + fail("do1_raw_tty_marked requires tty:"); + } + if (!cfg.do1_toggle_per_frame) { + fail("do1_raw_tty_marked requires do1_toggle_per_frame"); + } + if (cfg.do1_pair_subtract_avg) { + fail("do1_raw_tty_marked is incompatible with do1_pair_subtract_avg"); + } + if (cfg.mode != X502_LCH_MODE_DIFF) { + fail("do1_raw_tty_marked requires phase configuration: mode:diff channels:2 ch1:2 ch2:3"); + } + if ((cfg.channel_count != 2U) || (cfg.ch1 != 2U) || (cfg.ch2 != 3U)) { + fail("do1_raw_tty_marked requires phase configuration: mode:diff channels:2 ch1:2 ch2:3"); + } + } + if (cfg.do1_pair_subtract_avg) { + if (!cfg.tty_path.has_value()) { + fail("do1_pair_subtract_avg requires tty:"); + } + if (!cfg.do1_toggle_per_frame) { + fail("do1_pair_subtract_avg requires do1_toggle_per_frame"); + } + if (cfg.do1_noise_subtract) { + fail("do1_pair_subtract_avg is incompatible with do1_noise_subtract"); + } + if (cfg.do1_raw_tty_marked) { + fail("do1_pair_subtract_avg is incompatible with do1_raw_tty_marked"); + } + if (cfg.profile != CaptureProfile::Phase) { + fail("do1_pair_subtract_avg requires profile:phase"); + } + if ((cfg.mode != X502_LCH_MODE_DIFF) || (cfg.channel_count != 2U) || (cfg.ch1 != 2U) || (cfg.ch2 != 3U)) { + fail("do1_pair_subtract_avg requires phase configuration: mode:diff channels:2 ch1:2 ch2:3"); + } + } + if (cfg.do8_freq_ref) { + if (!cfg.tty_path.has_value()) { + fail("do8_freq_ref requires tty:"); + } + if (!cfg.do1_toggle_per_frame) { + fail("do8_freq_ref requires do1_toggle_per_frame (cyclic output must be active)"); + } + if (cfg.do8_cycle_period_ticks < 4U) { + fail("do8_cycle_period must be >= 4 (at least 2 LOW ticks + 2 HIGH ticks)"); + } + if (cfg.do8_amplitude_threshold.has_value() && *cfg.do8_amplitude_threshold < 0.0) { + fail("do8_threshold must be >= 0"); + } + if (std::gcd(4U, cfg.do8_cycle_period_ticks) > 2U) { + std::cerr << "Warning: gcd(DO1 period=4" + << ", DO8 period=" << cfg.do8_cycle_period_ticks + << ") > 2; DI8=HIGH may systematically align with the same DO1 phase\n"; + } + } + if (sync_uses_di_syn1(cfg.sync_mode) && sync_uses_di_syn1(cfg.sync_start_mode)) { + fail("clock and start cannot both use DI_SYN1; use start_in or immediate start"); + } + if (sync_uses_di_syn2(cfg.sync_mode) && sync_uses_di_syn2(cfg.sync_start_mode)) { + fail("clock and start cannot both use DI_SYN2; use start_in or immediate start"); + } + if ((cfg.stop_mode != StopMode::TargetFrames) && sync_uses_di_syn2(cfg.sync_mode)) { + fail("DI_SYN2 cannot be used simultaneously for clock and stop"); + } + if ((cfg.stop_mode == StopMode::DiSyn2Rise) && (cfg.sync_start_mode == X502_SYNC_DI_SYN2_RISE)) { + fail("start and stop cannot both use the same DI_SYN2 rising edge"); + } + if ((cfg.stop_mode == StopMode::DiSyn2Fall) && (cfg.sync_start_mode == X502_SYNC_DI_SYN2_FALL)) { + fail("start and stop cannot both use the same DI_SYN2 falling edge"); + } + + if (cfg.mode == X502_LCH_MODE_DIFF) { + if (cfg.ch1 >= X502_ADC_DIFF_CH_CNT) { + fail("For differential mode E-502 ch1 must be in range 0..15"); + } + if ((cfg.channel_count >= 2U) && (cfg.ch2 >= X502_ADC_DIFF_CH_CNT)) { + fail("For differential mode E-502 ch2 must be in range 0..15"); + } + } else { + if (cfg.ch1 >= X502_ADC_COMM_CH_CNT) { + fail("For common-ground mode E-502 ch1 must be in range 0..31"); + } + if ((cfg.channel_count >= 2U) && (cfg.ch2 >= X502_ADC_COMM_CH_CNT)) { + fail("For common-ground mode E-502 ch2 must be in range 0..31"); + } + } + + 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& 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 +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(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(addr); +#endif +} + +template +Fn try_load_symbol(ModuleHandle module, const char* name) { +#ifdef _WIN32 + const auto addr = GetProcAddress(module, name); + return reinterpret_cast(addr); +#else + dlerror(); + void* addr = dlsym(module, name); + (void) dlerror(); + return reinterpret_cast(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_SetLChannelCount) SetLChannelCount = nullptr; + decltype(&X502_SetLChannel) SetLChannel = nullptr; + decltype(&X502_SetAdcFreqDivider) SetAdcFreqDivider = nullptr; + decltype(&X502_SetAdcInterframeDelay) SetAdcInterframeDelay = nullptr; + decltype(&X502_SetDinFreqDivider) SetDinFreqDivider = nullptr; + decltype(&X502_SetAdcFreq) SetAdcFreq = nullptr; + decltype(&X502_GetAdcFreq) GetAdcFreq = nullptr; + decltype(&X502_SetDinFreq) SetDinFreq = nullptr; + decltype(&X502_SetOutFreq) SetOutFreq = nullptr; + decltype(&X502_AsyncOutDig) AsyncOutDig = nullptr; + decltype(&X502_AsyncInDig) AsyncInDig = nullptr; + decltype(&X502_SetRefFreq) SetRefFreq = 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; + decltype(&X502_GetRecvReadyCount) GetRecvReadyCount = nullptr; + decltype(&X502_Recv) Recv = nullptr; + decltype(&X502_OutCycleLoadStart) OutCycleLoadStart = nullptr; + decltype(&X502_OutCycleSetup) OutCycleSetup = nullptr; + decltype(&X502_OutCycleStop) OutCycleStop = nullptr; + decltype(&X502_Send) Send = nullptr; + decltype(&X502_PrepareData) PrepareData = 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(x502_module, "X502_Create"); + Free = load_symbol(x502_module, "X502_Free"); + Close = load_symbol(x502_module, "X502_Close"); + GetErrorString = load_symbol(x502_module, "X502_GetErrorString"); + GetDevInfo = try_load_symbol(x502_module, "X502_GetDevInfo"); + GetDevInfo2 = try_load_symbol(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(x502_module, "X502_SetMode"); + StreamsStop = load_symbol(x502_module, "X502_StreamsStop"); + StreamsDisable = load_symbol(x502_module, "X502_StreamsDisable"); + SetSyncMode = load_symbol(x502_module, "X502_SetSyncMode"); + SetSyncStartMode = load_symbol(x502_module, "X502_SetSyncStartMode"); + SetLChannelCount = load_symbol(x502_module, "X502_SetLChannelCount"); + SetLChannel = load_symbol(x502_module, "X502_SetLChannel"); + SetAdcFreqDivider = load_symbol(x502_module, "X502_SetAdcFreqDivider"); + SetAdcInterframeDelay = load_symbol(x502_module, "X502_SetAdcInterframeDelay"); + SetDinFreqDivider = load_symbol(x502_module, "X502_SetDinFreqDivider"); + SetAdcFreq = load_symbol(x502_module, "X502_SetAdcFreq"); + GetAdcFreq = load_symbol(x502_module, "X502_GetAdcFreq"); + SetDinFreq = load_symbol(x502_module, "X502_SetDinFreq"); + SetOutFreq = load_symbol(x502_module, "X502_SetOutFreq"); + AsyncOutDig = load_symbol(x502_module, "X502_AsyncOutDig"); + AsyncInDig = load_symbol(x502_module, "X502_AsyncInDig"); + SetRefFreq = load_symbol(x502_module, "X502_SetRefFreq"); + SetStreamBufSize = load_symbol(x502_module, "X502_SetStreamBufSize"); + SetStreamStep = load_symbol(x502_module, "X502_SetStreamStep"); + SetDigInPullup = load_symbol(x502_module, "X502_SetDigInPullup"); + SetExtRefFreqValue = load_symbol(x502_module, "X502_SetExtRefFreqValue"); + Configure = load_symbol(x502_module, "X502_Configure"); + StreamsEnable = load_symbol(x502_module, "X502_StreamsEnable"); + StreamsStart = load_symbol(x502_module, "X502_StreamsStart"); + GetRecvReadyCount = load_symbol(x502_module, "X502_GetRecvReadyCount"); + Recv = load_symbol(x502_module, "X502_Recv"); + OutCycleLoadStart = try_load_symbol(x502_module, "X502_OutCycleLoadStart"); + OutCycleSetup = try_load_symbol(x502_module, "X502_OutCycleSetup"); + OutCycleStop = try_load_symbol(x502_module, "X502_OutCycleStop"); + Send = load_symbol(x502_module, "X502_Send"); + PrepareData = load_symbol(x502_module, "X502_PrepareData"); + ProcessData = load_symbol(x502_module, "X502_ProcessData"); + + OpenUsb = load_symbol(e502_module, "E502_OpenUsb"); + OpenByIpAddr = load_symbol(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)); + } +} + +constexpr uint32_t kE502DiSyn2Mask = + (static_cast(1U) << 13U) | (static_cast(1U) << 17U); +constexpr uint32_t kE502Digital1Mask = (static_cast(1U) << 0U); +constexpr uint32_t kE502Digital2Mask = (static_cast(1U) << 1U); +constexpr uint32_t kE502Do1Mask = (static_cast(1U) << 0U); +constexpr uint32_t kE502Do2Mask = (static_cast(1U) << 1U); +constexpr uint32_t kE502Digital8Mask = (static_cast(1U) << 6U); // DI8 reads as bit 6 (hardware offset confirmed by loopback test) +constexpr uint32_t kE502Do8Mask = (static_cast(1U) << 7U); +constexpr uint32_t kDo1TogglePeriodTicks = 2U; +constexpr uint32_t kDo1CyclePatternWords = kDo1TogglePeriodTicks * 2U; +constexpr uint32_t kDo8HighTicks = 2U; +constexpr uint16_t kTtyMarkerDi8High = 0x00A8U; +constexpr uint32_t kStreamInputAdcFlag = 0x80000000U; +constexpr uint32_t kStreamInputCalibratedAdcFlag = 0x40000000U; + +using TickMs = uint64_t; + +TickMs tick_count_ms() { +#ifdef _WIN32 + return static_cast(GetTickCount64()); +#else + using namespace std::chrono; + return static_cast( + duration_cast(steady_clock::now().time_since_epoch()).count()); +#endif +} + +#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 sig) { + if (g_console_stop_requested != 0) { + // Already asked to stop and signalled again (e.g. a second Ctrl+C, or the + // parent escalating): the graceful device teardown can block, so exit + // immediately rather than ever hang. The kernel releases the E-502 on exit. + std::_Exit(128 + sig); + } + g_console_stop_requested = 1; +} + +bool console_stop_requested() { + return g_console_stop_requested != 0; +} +#endif + +enum class PacketCloseReason { + ExternalStopEdge, + DurationLimit, + UserStop +}; + +const char* packet_close_reason_to_string(PacketCloseReason reason) { + switch (reason) { + case PacketCloseReason::ExternalStopEdge: + return "DI_SYN2 edge"; + case PacketCloseReason::DurationLimit: + return "duration limit"; + case PacketCloseReason::UserStop: + return "user stop"; + default: + return "unknown"; + } +} + +bool matches_sync_edge(uint32_t mode, bool prev_level, bool current_level) { + if (mode == X502_SYNC_DI_SYN2_RISE) { + return !prev_level && current_level; + } + if (mode == X502_SYNC_DI_SYN2_FALL) { + return prev_level && !current_level; + } + return false; +} + +bool matches_stop_edge(StopMode mode, bool prev_level, bool current_level) { + if (mode == StopMode::DiSyn2Rise) { + return !prev_level && current_level; + } + if (mode == StopMode::DiSyn2Fall) { + return prev_level && !current_level; + } + return false; +} + +struct PacketAccumulator { + std::array, 2> channels; + std::vector di1; + std::size_t zeroed_samples = 0; + std::size_t stored_samples = 0; + bool pending_frame_di1_valid = false; + uint8_t pending_frame_di1 = 0; + + void reset(std::size_t reserve_frames, std::size_t channel_count) { + for (std::size_t i = 0; i < channels.size(); ++i) { + auto& channel = channels[i]; + channel.clear(); + if (i < channel_count) { + channel.reserve(reserve_frames); + } + } + di1.clear(); + di1.reserve(reserve_frames); + zeroed_samples = 0; + stored_samples = 0; + pending_frame_di1_valid = false; + pending_frame_di1 = 0; + } + + std::size_t frame_count(std::size_t channel_count) const { + std::size_t frames = (channel_count <= 1U) ? channels[0].size() : std::min(channels[0].size(), channels[1].size()); + if (!di1.empty()) { + frames = std::min(frames, di1.size()); + } + return frames; + } +}; + +struct TtyContinuousState { + bool pending_ch1_valid = false; + int16_t pending_ch1 = 0; + uint16_t next_index = 1; +}; + +struct TtyGroupAverageState { + double sum_ch1 = 0.0; + double sum_ch2 = 0.0; + uint32_t count_ch1 = 0; + uint32_t count_ch2 = 0; + uint32_t next_index = 1; + + void clear_step() { + sum_ch1 = 0.0; + sum_ch2 = 0.0; + count_ch1 = 0; + count_ch2 = 0; + } + + void reset_packet() { + clear_step(); + next_index = 1; + } + + void add_sample(uint32_t lch, double raw_code) { + if (lch == 0U) { + sum_ch1 += raw_code; + ++count_ch1; + } else if (lch == 1U) { + sum_ch2 += raw_code; + ++count_ch2; + } + } + + bool has_complete_step(uint32_t channel_count) const { + return (count_ch1 != 0U) && ((channel_count <= 1U) || (count_ch2 != 0U)); + } +}; + +struct Do1RawTaggedState { + std::array step_sum {}; + std::array step_count {}; + bool step_level_initialized = false; + bool step_di1_high = false; + uint16_t next_index = 1; + + void clear_step() { + step_sum = {}; + step_count = {}; + } + + void reset_packet() { + clear_step(); + step_level_initialized = false; + step_di1_high = false; + next_index = 1; + } + + void start_new_step(bool di1_high) { + clear_step(); + step_di1_high = di1_high; + step_level_initialized = true; + } + + void add_sample(uint32_t lch, double raw_code) { + if (lch >= step_sum.size()) { + return; + } + step_sum[lch] += raw_code; + ++step_count[lch]; + } + + bool has_complete_step(uint32_t channel_count) const { + return (step_count[0] != 0U) && ((channel_count <= 1U) || (step_count[1] != 0U)); + } + + double step_average(uint32_t lch) const { + if ((lch >= step_sum.size()) || (step_count[lch] == 0U)) { + return 0.0; + } + return step_sum[lch] / static_cast(step_count[lch]); + } +}; + +struct Do1NoiseSubtractState { + std::array step_sum {}; + std::array step_count {}; + std::array last_high_avg {}; + std::array last_high_valid {}; + bool high_baseline_pending = false; + bool step_level_initialized = false; + bool step_di1_high = false; + uint16_t next_index = 1; + + void configure(uint32_t history_steps) { + (void) history_steps; + clear_baseline(); + reset_packet(); + } + + void clear_step() { + step_sum = {}; + step_count = {}; + } + + void clear_baseline() { + last_high_avg = {}; + last_high_valid = {}; + high_baseline_pending = false; + } + + void reset_packet() { + clear_step(); + step_level_initialized = false; + step_di1_high = false; + next_index = 1; + } + + void start_new_step(bool di1_high) { + clear_step(); + step_di1_high = di1_high; + step_level_initialized = true; + } + + void add_sample(uint32_t lch, double raw_code) { + if (lch >= step_sum.size()) { + return; + } + step_sum[lch] += raw_code; + ++step_count[lch]; + } + + bool has_complete_step(uint32_t channel_count) const { + return (step_count[0] != 0U) && ((channel_count <= 1U) || (step_count[1] != 0U)); + } + + double step_average(uint32_t lch) const { + if ((lch >= step_sum.size()) || (step_count[lch] == 0U)) { + return 0.0; + } + return step_sum[lch] / static_cast(step_count[lch]); + } + + void update_last_high(uint32_t lch, double avg_value) { + if (lch >= last_high_avg.size()) { + return; + } + last_high_avg[lch] = avg_value; + last_high_valid[lch] = true; + } + + bool has_pending_high(uint32_t channel_count) const { + if (!high_baseline_pending) { + return false; + } + if (!last_high_valid[0]) { + return false; + } + if ((channel_count > 1U) && !last_high_valid[1]) { + return false; + } + return true; + } + + void mark_high_pending() { high_baseline_pending = true; } + + void consume_pending_high() { high_baseline_pending = false; } + + double last_high(uint32_t lch) const { + if (lch >= last_high_avg.size()) { + return 0.0; + } + return last_high_avg[lch]; + } + + void finish_step() { clear_step(); } +}; + +struct Do1PairSubtractAverageState { + struct PairSample { + double ch1 = 0.0; + double ch2 = 0.0; + }; + + std::deque low_pairs; + std::deque high_pairs; + bool run_initialized = false; + bool run_di1_high = false; + double diff_sum_ch1 = 0.0; + double diff_sum_ch2 = 0.0; + uint32_t diff_count = 0; + + bool pending_ch1_valid = false; + double pending_ch1_raw = 0.0; + bool pending_ch1_di1_high = false; + bool pending_ch1_di2_high = false; + bool pending_ch1_di8_high = false; // raw DI8 level (not guard-filtered) + + uint16_t next_index = 1; + + void clear_run_pairs() { + low_pairs.clear(); + high_pairs.clear(); + diff_sum_ch1 = 0.0; + diff_sum_ch2 = 0.0; + diff_count = 0; + } + + void clear_pending_ch1() { + pending_ch1_valid = false; + pending_ch1_raw = 0.0; + pending_ch1_di1_high = false; + pending_ch1_di2_high = false; + pending_ch1_di8_high = false; + } + + void reset_packet() { + clear_run_pairs(); + run_initialized = false; + run_di1_high = false; + clear_pending_ch1(); + next_index = 1; + } + + void start_run(bool di1_high) { + clear_run_pairs(); + run_initialized = true; + run_di1_high = di1_high; + } + + void clear_run() { + clear_run_pairs(); + run_initialized = false; + } + + void add_pending_ch1(double raw_code, bool di1_high, bool di2_high, + bool di8_high_level = false) { + pending_ch1_valid = true; + pending_ch1_raw = raw_code; + pending_ch1_di1_high = di1_high; + pending_ch1_di2_high = di2_high; + pending_ch1_di8_high = di8_high_level; + } + + void add_pair_sample(bool di2_high, double ch1_value, double ch2_value) { + const PairSample sample{ch1_value, ch2_value}; + if (di2_high) { + high_pairs.push_back(sample); + } else { + low_pairs.push_back(sample); + } + + while (!low_pairs.empty() && !high_pairs.empty()) { + const PairSample low = low_pairs.front(); + const PairSample high = high_pairs.front(); + low_pairs.pop_front(); + high_pairs.pop_front(); + + diff_sum_ch1 += (low.ch1 - high.ch1); + diff_sum_ch2 += (low.ch2 - high.ch2); + ++diff_count; + } + } + + bool has_output() const { return diff_count != 0U; } + + double avg_diff_ch1() const { + if (diff_count == 0U) { + return 0.0; + } + return diff_sum_ch1 / static_cast(diff_count); + } + + double avg_diff_ch2() const { + if (diff_count == 0U) { + return 0.0; + } + return diff_sum_ch2 / static_cast(diff_count); + } +}; + +struct Di8FreqRefState { + std::array high_sum {}; + std::array high_count {}; + + static constexpr uint32_t kGuardSamples = 2U; + bool di8_prev_level = false; + bool di8_initialized = false; + uint32_t samples_since_transition = 0; + bool in_guard = false; + + uint16_t last_standard_index = 0; + bool last_standard_valid = false; + + void clear_step() { + high_sum = {}; + high_count = {}; + } + + void reset_packet() { + clear_step(); + di8_prev_level = false; + di8_initialized = false; + samples_since_transition = 0; + in_guard = false; + last_standard_index = 0; + last_standard_valid = false; + } + + // Returns true if the sample is in the settled DI8=HIGH region. + // Returns false for guard samples and DI8=LOW. + // Sets in_guard for samples within the guard window after any transition. + bool update_di8(bool di8_level) { + if (!di8_initialized) { + di8_prev_level = di8_level; + di8_initialized = true; + in_guard = true; + samples_since_transition = 0; + return false; + } + + if (di8_level != di8_prev_level) { + di8_prev_level = di8_level; + samples_since_transition = 0; + in_guard = true; + return false; + } + + if (in_guard) { + ++samples_since_transition; + if (samples_since_transition >= kGuardSamples) { + in_guard = false; + } else { + return false; + } + } + + return di8_level; + } + + void add_sample(uint32_t lch, double raw_code) { + if (lch >= high_sum.size()) { + return; + } + high_sum[lch] += raw_code; + ++high_count[lch]; + } + + bool has_complete_step(uint32_t channel_count) const { + return (high_count[0] != 0U) && ((channel_count <= 1U) || (high_count[1] != 0U)); + } + + double step_average(uint32_t lch) const { + if ((lch >= high_sum.size()) || (high_count[lch] == 0U)) { + return 0.0; + } + return high_sum[lch] / static_cast(high_count[lch]); + } +}; + +int16_t pack_raw_code_to_int16(double avg_raw_code) { + const double scaled = + avg_raw_code * 32767.0 / static_cast(X502_ADC_SCALE_CODE_MAX); + const long long rounded = std::llround(scaled); + const long long clamped = std::clamp(rounded, -32768LL, 32767LL); + return static_cast(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(payload); + if ((payload & 0x00800000U) != 0U) { + value |= static_cast(0xFF000000U); + } + } else { + const uint32_t payload = word & 0x0000FFFFU; + value = static_cast(payload); + if ((payload & 0x00008000U) != 0U) { + value |= static_cast(0xFFFF0000U); + } + } + + raw_code = static_cast(value); + return true; +} + +struct ConsoleCtrlGuard { + bool installed = false; + +#ifndef _WIN32 + bool sigint_installed = false; + bool sigterm_installed = false; + bool sigabrt_installed = false; + struct sigaction old_sigint {}; + struct sigaction old_sigterm {}; + struct sigaction old_sigabrt {}; +#endif + + ConsoleCtrlGuard() { +#ifdef _WIN32 + InterlockedExchange(&g_console_stop_requested, 0); + installed = SetConsoleCtrlHandler(console_ctrl_handler, TRUE) != 0; +#else + g_console_stop_requested = 0; + + struct sigaction action {}; + action.sa_handler = console_ctrl_handler; + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + + sigint_installed = sigaction(SIGINT, &action, &old_sigint) == 0; + sigterm_installed = sigaction(SIGTERM, &action, &old_sigterm) == 0; + sigabrt_installed = sigaction(SIGABRT, &action, &old_sigabrt) == 0; + installed = sigint_installed && sigterm_installed && sigabrt_installed; +#endif + } + + ~ConsoleCtrlGuard() { +#ifdef _WIN32 + if (installed) { + SetConsoleCtrlHandler(console_ctrl_handler, FALSE); + } + InterlockedExchange(&g_console_stop_requested, 0); +#else + if (sigint_installed) { + sigaction(SIGINT, &old_sigint, nullptr); + } + if (sigterm_installed) { + sigaction(SIGTERM, &old_sigterm, nullptr); + } + if (sigabrt_installed) { + sigaction(SIGABRT, &old_sigabrt, nullptr); + } + g_console_stop_requested = 0; +#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(info.fpga_ver >> 8) << "." + << static_cast(info.fpga_ver & 0xFF) << "\n" + << "PLDA version: " << static_cast(info.plda_ver) << "\n" + << "Board revision: " << static_cast(info.board_rev) << "\n" + << "MCU firmware: " << info.mcu_firmware_ver << "\n"; +} + +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{}; + 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); + + 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, cfg.sync_mode), "Set sync mode"); + expect_ok(api, api.SetSyncStartMode(device.hnd, cfg.sync_start_mode), "Set sync start mode"); + + if (cfg.sync_mode == X502_SYNC_INTERNAL) { + expect_ok(api, api.SetRefFreq(device.hnd, cfg.internal_ref_freq), "Set internal reference frequency"); + } else { + const int32_t ext_ref_err = api.SetExtRefFreqValue(device.hnd, cfg.sample_clock_hz); + if (ext_ref_err != X502_ERR_OK) { + if (cfg.sample_clock_hz <= 1500000.0) { + expect_ok(api, ext_ref_err, "Set external reference frequency"); + } else { + std::cerr << "Warning: X502_SetExtRefFreqValue(" << cfg.sample_clock_hz + << ") failed, continuing with manual divider configuration: " + << x502_error(api, ext_ref_err) << "\n"; + } + } + } + + expect_ok(api, api.SetLChannelCount(device.hnd, cfg.channel_count), "Set logical channel count"); + expect_ok(api, api.SetLChannel(device.hnd, 0, cfg.ch1, cfg.mode, cfg.range, 1), "Set logical channel 0"); + if (cfg.channel_count >= 2U) { + expect_ok(api, api.SetLChannel(device.hnd, 1, cfg.ch2, cfg.mode, cfg.range, 1), "Set logical channel 1"); + } + + const bool internal_max_clock = use_internal_max_clock(cfg); + double actual_sample_clock_hz = cfg.sample_clock_hz; + double actual_frame_freq_hz = cfg.sample_clock_hz / 2.0; + if (internal_max_clock) { + actual_sample_clock_hz = static_cast(cfg.internal_ref_freq); + actual_frame_freq_hz = 0.0; + expect_ok(api, api.SetAdcFreq(device.hnd, &actual_sample_clock_hz, nullptr), "Set ADC frequency to maximum"); + expect_ok(api, api.GetAdcFreq(device.hnd, &actual_sample_clock_hz, &actual_frame_freq_hz), + "Get effective ADC frequency"); + } else { + expect_ok(api, api.SetAdcFreq(device.hnd, &actual_sample_clock_hz, &actual_frame_freq_hz), "Set ADC frequency"); + } + + double actual_din_freq_hz = actual_sample_clock_hz; + expect_ok(api, api.SetDinFreq(device.hnd, &actual_din_freq_hz), "Set DIN frequency"); + if (std::fabs(actual_din_freq_hz - actual_sample_clock_hz) > std::max(0.5, actual_sample_clock_hz * 1e-6)) { + std::ostringstream message; + message << "ADC and DIN frequencies no longer match closely enough for 1:1 masking logic. " + << "ADC=" << actual_sample_clock_hz << " Hz, DIN=" << actual_din_freq_hz << " Hz."; + fail(message.str()); + } + const double combined_input_rate_hz = actual_sample_clock_hz + actual_din_freq_hz; + if (cfg.ip_addr.has_value() && (combined_input_rate_hz > 2500000.0)) { + std::ostringstream message; + message << "Current Ethernet input load is too high for E-502: ADC " + << actual_sample_clock_hz << " Hz + DIN " << actual_din_freq_hz + << " Hz = " << combined_input_rate_hz + << " words/s, while the documented Ethernet input-only limit is about 2500000 words/s."; + fail(message.str()); + } + double actual_dout_freq_hz = actual_frame_freq_hz; + if (cfg.do1_toggle_per_frame) { + if (actual_sample_clock_hz <= 0.0) { + fail("do1_toggle_per_frame requires a positive ADC sample rate"); + } + actual_dout_freq_hz = actual_sample_clock_hz; + expect_ok(api, api.SetOutFreq(device.hnd, &actual_dout_freq_hz), "Set DOUT 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"); + if (cfg.do1_toggle_per_frame) { + expect_ok(api, api.SetStreamBufSize(device.hnd, X502_STREAM_CH_OUT, cfg.input_buffer_words), "Set output buffer size"); + expect_ok(api, api.SetStreamStep(device.hnd, X502_STREAM_CH_OUT, cfg.input_step_words), "Set output stream step"); + } + + uint32_t pullups = 0; + if (cfg.pullup_syn1) { + pullups |= X502_PULLUPS_DI_SYN1; + } + if (cfg.pullup_syn2) { + pullups |= X502_PULLUPS_DI_SYN2; + } + if (cfg.pulldown_conv_in) { + pullups |= X502_PULLDOWN_CONV_IN; + } + if (cfg.pulldown_start_in) { + pullups |= X502_PULLDOWN_START_IN; + } + expect_ok(api, api.SetDigInPullup(device.hnd, pullups), "Set digital input pullups/pulldowns"); + + expect_ok(api, api.Configure(device.hnd, 0), "Configure device"); + expect_ok(api, api.AsyncOutDig(device.hnd, kE502Do2Mask, 0U), "Force DO2 HIGH"); + + // DO8/DI8 loopback diagnostic (before streams start, AsyncOutDig/AsyncInDig work) + if (cfg.do8_freq_ref) { + uint32_t din_val = 0; + // Set DO8 HIGH + expect_ok(api, api.AsyncOutDig(device.hnd, kE502Do2Mask | kE502Do8Mask, 0U), + "DO8 loopback test: set DO8 HIGH"); + usleep(2000); + expect_ok(api, api.AsyncInDig(device.hnd, &din_val), "DO8 loopback test: read DIN"); + const bool di8_high = (din_val & kE502Digital8Mask) != 0U; + std::cerr << "DO8/DI8 loopback: DO8=HIGH -> DI8=" << (di8_high ? "HIGH" : "LOW") + << " (DIN=0x" << std::hex << din_val << std::dec << ")\n"; + // Set DO8 LOW + expect_ok(api, api.AsyncOutDig(device.hnd, kE502Do2Mask, 0U), + "DO8 loopback test: set DO8 LOW"); + usleep(2000); + expect_ok(api, api.AsyncInDig(device.hnd, &din_val), "DO8 loopback test: read DIN"); + const bool di8_low = (din_val & kE502Digital8Mask) != 0U; + std::cerr << "DO8/DI8 loopback: DO8=LOW -> DI8=" << (di8_low ? "HIGH" : "LOW") + << " (DIN=0x" << std::hex << din_val << std::dec << ")\n"; + if (!di8_high || di8_low) { + std::cerr << "WARNING: DO8/DI8 loopback FAILED. Check physical connection.\n"; + } else { + std::cerr << "DO8/DI8 loopback PASSED.\n"; + } + // Restore + expect_ok(api, api.AsyncOutDig(device.hnd, kE502Do2Mask, 0U), "Restore DO2 HIGH only"); + } + uint32_t enabled_streams = X502_STREAM_ADC | X502_STREAM_DIN; + if (cfg.do1_toggle_per_frame) { + enabled_streams |= X502_STREAM_DOUT; + } + expect_ok(api, api.StreamsEnable(device.hnd, enabled_streams), "Enable data streams"); + + const std::size_t target_frames = std::max( + 1, static_cast(std::llround((cfg.duration_ms / 1000.0) * actual_frame_freq_hz))); + + std::cout << "Capture settings:\n" + << " clock source: " << sync_mode_to_string(cfg.sync_mode, false) << "\n" + << " start source: " << sync_mode_to_string(cfg.sync_start_mode, true) << "\n" + << " stop source: " << stop_mode_to_string(cfg.stop_mode) << "\n" + << " sample clock: " << actual_sample_clock_hz << " Hz"; + if (cfg.sync_mode == X502_SYNC_INTERNAL) { + std::cout << " (internal ref " << ref_freq_to_string(cfg.internal_ref_freq) << " Hz)"; + if (internal_max_clock) { + std::cout << ", maximum ADC speed"; + } + } + std::cout << "\n" + << " DIN clock: " << actual_din_freq_hz << " Hz\n" + << " ADC+DIN total input rate: " << combined_input_rate_hz << " words/s\n" + << " ADC logical channels: " << cfg.channel_count << "\n" + << " per-channel frame rate: " << actual_frame_freq_hz << " Hz\n" + << " duration: " << cfg.duration_ms << " ms\n" + << " packet limit: " + << ((cfg.packet_limit == 0U) ? std::string("continuous until Ctrl+C") + : std::to_string(cfg.packet_limit) + " packet(s)") << "\n" + << " live update period: " + << ((cfg.live_update_period_ms == 0U) ? std::string("every packet") + : std::to_string(cfg.live_update_period_ms) + " ms") << "\n" + << " channel 1: " << phy_channel_name(cfg.mode, cfg.ch1) << "\n" + << " channel 2: " + << ((cfg.channel_count >= 2U) ? phy_channel_name(cfg.mode, cfg.ch2) : std::string("disabled")) << "\n" + << " DI1 handling: " << di1_mode_to_string(cfg.di1_mode) << "\n" + << " DO1 toggle per frame: " + << (cfg.do1_toggle_per_frame ? std::string("enabled (hardware cyclic 00110011..., toggle every 2 ADC ticks)") + : std::string("disabled")) << "\n" + << " DO2 state: forced HIGH\n" + << " DOUT rate: " + << (cfg.do1_toggle_per_frame ? std::to_string(actual_dout_freq_hz) + " Hz" : std::string("disabled")) << "\n" + << " DO8 freq ref: " + << (cfg.do8_freq_ref + ? ("enabled (period=" + std::to_string(cfg.do8_cycle_period_ticks) + + " ticks, high=" + std::to_string(kDo8HighTicks) + " ticks" + + (cfg.do8_amplitude_threshold.has_value() + ? ", threshold=" + std::to_string(*cfg.do8_amplitude_threshold) : "") + + ")") + : std::string("disabled")) + << "\n" + << " ADC range: +/-" << range_to_volts(cfg.range) << " V\n" + << " max frames per packet per channel: " << target_frames << "\n"; + + const uint32_t read_capacity_words = std::max(cfg.recv_block_words, cfg.input_step_words); + std::size_t tty_ring_capacity_bytes = 0; + std::unique_ptr tty_writer; + const bool tty_di1_group_average = cfg.tty_path.has_value() && cfg.di1_group_average; + const bool tty_do1_noise_subtract = cfg.tty_path.has_value() && cfg.do1_noise_subtract; + const bool tty_do1_raw_tty_marked = cfg.tty_path.has_value() && cfg.do1_raw_tty_marked; + const bool tty_do1_pair_subtract_avg = cfg.tty_path.has_value() && cfg.do1_pair_subtract_avg; + const bool fast_tty_avg_stream_mode = + tty_di1_group_average || tty_do1_noise_subtract || tty_do1_raw_tty_marked || tty_do1_pair_subtract_avg; + const uint16_t tty_packet_start_marker = + (cfg.profile == CaptureProfile::Amplitude) ? 0x001AU : 0x000AU; + const std::string fast_tty_mode_name = tty_do1_noise_subtract + ? std::string("do1_noise_subtract") + : (tty_do1_raw_tty_marked ? std::string("do1_raw_tty_marked") + : (tty_do1_pair_subtract_avg ? std::string("do1_pair_subtract_avg") + : (tty_di1_group_average ? std::string("di1_group_avg") + : std::string("none")))); + if (cfg.tty_path) { + const uint64_t typical_tty_batch_frames = (static_cast(read_capacity_words) + 1U) / 2U; + const uint64_t typical_tty_batch_bytes = typical_tty_batch_frames * sizeof(uint16_t) * 4U; + const uint64_t required_tty_ring_bytes = typical_tty_batch_bytes * 16U; + const uint64_t tty_ring_bytes_u64 = std::max(1024U * 1024U, required_tty_ring_bytes); + if (tty_ring_bytes_u64 > static_cast(std::numeric_limits::max())) { + fail("TTY ring buffer size overflowed size_t"); + } + tty_ring_capacity_bytes = static_cast(tty_ring_bytes_u64); + tty_writer = std::make_unique(*cfg.tty_path, tty_ring_capacity_bytes); + if (!tty_di1_group_average && !tty_do1_noise_subtract && !tty_do1_raw_tty_marked && + !tty_do1_pair_subtract_avg) { + tty_writer->emit_packet_start(tty_packet_start_marker); + } + } + std::unique_ptr writer; + if (!fast_tty_avg_stream_mode) { + writer = std::make_unique(cfg.csv_path, cfg.svg_path, cfg.live_html_path, cfg.live_json_path); + writer->initialize_live_plot(); + writer->initialize_csv(cfg.channel_count, cfg.di1_mode == Di1Mode::Trace); + } + if (fast_tty_avg_stream_mode) { + std::cout << " tty fast stream-only mode: enabled (" << fast_tty_mode_name << ")\n" + << " tty stream output: " << *cfg.tty_path << "\n" + << " tty ring buffer bytes: " << tty_ring_capacity_bytes << "\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" + << " tty di1_group_avg: " << (tty_di1_group_average ? "enabled" : "disabled") << "\n" + << " tty do1_noise_subtract: " << (tty_do1_noise_subtract ? "enabled" : "disabled") << "\n" + << " tty do1_raw_tty_marked: " << (tty_do1_raw_tty_marked ? "enabled" : "disabled") << "\n" + << " tty do1_pair_subtract_avg: " << (tty_do1_pair_subtract_avg ? "enabled" : "disabled") << "\n" + << " do1_noise_subtract marker: " + << (tty_do1_noise_subtract ? "DI1 from DIN (DO1->DI1 loopback expected)" : "n/a") << "\n" + << " do1_raw_tty_marked markers: " + << (tty_do1_raw_tty_marked ? "0x00A3=LOW, 0x00A4=HIGH (DI1 from DIN)" : "n/a") << "\n" + << " do1_pair_subtract_avg marker: " + << (tty_do1_pair_subtract_avg ? "word0=0x000A, low-high by DI2, averaged per DI1 run" : "n/a") << "\n" + << " noise_avg_steps: " + << (tty_do1_noise_subtract ? std::to_string(*cfg.noise_avg_steps) : std::string("n/a")) << "\n" + << " stream-only note: ignoring csv/svg/live_html/live_json/live_update_period_ms/svg_history_packets\n"; + } else { + std::cout << " live plot html: " << writer->live_html_path() << "\n" + << " live plot data: " << writer->live_json_path() << "\n" + << " tty stream output: " << (cfg.tty_path ? *cfg.tty_path : std::string("disabled")) << "\n" + << " tty ring buffer bytes: " + << (cfg.tty_path ? std::to_string(tty_ring_capacity_bytes) : std::string("disabled")) << "\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" + << " tty di1_group_avg: " + << (tty_di1_group_average ? std::string("enabled") + : (cfg.di1_group_average ? std::string("requested, tty disabled") + : std::string("disabled"))) + << "\n" + << " tty do1_noise_subtract: " + << (tty_do1_noise_subtract ? std::string("enabled") + : (cfg.do1_noise_subtract ? std::string("requested, tty disabled") + : std::string("disabled"))) + << "\n" + << " tty do1_raw_tty_marked: " + << (tty_do1_raw_tty_marked ? std::string("enabled") + : (cfg.do1_raw_tty_marked ? std::string("requested, tty disabled") + : std::string("disabled"))) + << "\n" + << " tty do1_pair_subtract_avg: " + << (tty_do1_pair_subtract_avg ? std::string("enabled") + : (cfg.do1_pair_subtract_avg ? std::string("requested, tty disabled") + : std::string("disabled"))) + << "\n" + << " SVG history packets kept in RAM: " + << ((cfg.svg_history_packets == 0U) ? std::string("all (unbounded)") : std::to_string(cfg.svg_history_packets)) + << "\n"; + } + + ConsoleCtrlGuard console_guard; + if (!console_guard.installed) { + std::cerr << "Warning: Ctrl+C handler could not be installed; continuous mode may stop abruptly.\n"; + } + + const uint32_t cycle_pattern_len = cfg.do8_freq_ref + ? static_cast(std::lcm(static_cast(kDo1CyclePatternWords), + cfg.do8_cycle_period_ticks)) + : kDo1CyclePatternWords; + std::vector do1_cycle_pattern(cycle_pattern_len, 0U); + std::vector do1_cycle_encoded(cycle_pattern_len, 0U); + if (cfg.do1_toggle_per_frame) { + if ((api.OutCycleLoadStart == nullptr) || (api.OutCycleSetup == nullptr) || (api.OutCycleStop == nullptr)) { + fail("do1_toggle_per_frame requires X502_OutCycle* API support (cyclic output mode)"); + } + + for (std::size_t i = 0; i < do1_cycle_pattern.size(); ++i) { + uint32_t word = kE502Do2Mask; + + const uint32_t do1_phase = static_cast(i) % kDo1CyclePatternWords; + if (do1_phase >= kDo1TogglePeriodTicks) { + word |= kE502Do1Mask; + } + + if (cfg.do8_freq_ref) { + const uint32_t do8_phase = static_cast(i) % cfg.do8_cycle_period_ticks; + if (do8_phase >= (cfg.do8_cycle_period_ticks - kDo8HighTicks)) { + word |= kE502Do8Mask; + } + } + + do1_cycle_pattern[i] = word; + } + + // Debug: print cyclic DOUT pattern + { + std::cerr << "Cyclic DOUT pattern (" << do1_cycle_pattern.size() << " words):"; + for (std::size_t i = 0; i < do1_cycle_pattern.size(); ++i) { + const uint32_t w = do1_cycle_pattern[i]; + const char do1_ch = (w & kE502Do1Mask) ? '1' : '0'; + const char do2_ch = (w & kE502Do2Mask) ? '1' : '0'; + const char do8_ch = (w & kE502Do8Mask) ? '1' : '0'; + std::cerr << "\n [" << i << "] 0x" << std::hex << w << std::dec + << " DO1=" << do1_ch << " DO2=" << do2_ch << " DO8=" << do8_ch; + } + std::cerr << "\n"; + } + + if (cycle_pattern_len > 262144U) { + fail("Combined DO1+DO8 cyclic pattern too large: " + std::to_string(cycle_pattern_len)); + } + + expect_ok(api, + api.OutCycleLoadStart(device.hnd, static_cast(do1_cycle_pattern.size())), + "Allocate cyclic DOUT buffer"); + expect_ok(api, + api.PrepareData(device.hnd, + nullptr, + nullptr, + do1_cycle_pattern.data(), + static_cast(do1_cycle_pattern.size()), + 0, + do1_cycle_encoded.data()), + "Prepare cyclic DOUT pattern"); + + std::size_t sent_cycle_words = 0; + while (sent_cycle_words < do1_cycle_encoded.size()) { + const int32_t sent = api.Send(device.hnd, + do1_cycle_encoded.data() + sent_cycle_words, + static_cast(do1_cycle_encoded.size() - sent_cycle_words), + cfg.recv_timeout_ms); + if (sent < 0) { + fail("Send cyclic DOUT pattern: " + x502_error(api, sent)); + } + if (sent == 0) { + fail("Send cyclic DOUT pattern timed out before all words were queued"); + } + sent_cycle_words += static_cast(sent); + } + + // Debug: print encoded pattern + { + std::cerr << "Encoded DOUT pattern (" << do1_cycle_encoded.size() << " words):"; + for (std::size_t i = 0; i < do1_cycle_encoded.size(); ++i) { + std::cerr << " 0x" << std::hex << do1_cycle_encoded[i] << std::dec; + } + std::cerr << "\nSent " << sent_cycle_words << " encoded words to device.\n"; + } + + expect_ok(api, api.OutCycleSetup(device.hnd, X502_OUT_CYCLE_FLAGS_WAIT_DONE), "Activate cyclic DOUT pattern"); + } + + expect_ok(api, api.StreamsStart(device.hnd), "Start streams"); + device.streams_started = true; + + std::vector raw(read_capacity_words); + std::vector adc_buffer(fast_tty_avg_stream_mode ? 0U : read_capacity_words); + std::vector adc_raw_buffer(read_capacity_words); + std::vector din_buffer(read_capacity_words); + std::deque pending_adc; + std::deque pending_adc_raw; + std::deque pending_din; + std::deque packets; + PacketAccumulator current_packet; + TtyContinuousState tty_state; + TtyGroupAverageState tty_group_state; + Do1RawTaggedState tty_do1_raw_state; + Do1NoiseSubtractState tty_do1_noise_state; + Do1PairSubtractAverageState tty_do1_pair_state; + Di8FreqRefState tty_di8_state; + std::vector tty_frame_words; + tty_frame_words.reserve(static_cast(read_capacity_words) * 2U + 16U); + if (tty_do1_noise_subtract) { + tty_do1_noise_state.configure(*cfg.noise_avg_steps); + } + if (!fast_tty_avg_stream_mode) { + current_packet.reset(target_frames, cfg.channel_count); + } + std::size_t csv_global_frame_index = 0; + std::size_t packet_avg_steps = 0; + std::size_t fast_packet_frames = 0; + uint64_t packet_clock_count = 0; + uint64_t packet_di2_high_clocks = 0; + uint64_t packet_di2_low_clocks = 0; + uint64_t packet_di8_high_clocks = 0; + uint64_t packet_di8_low_clocks = 0; + uint64_t packet_di8_samples_accumulated = 0; + uint64_t packet_di8_ref_frames_emitted = 0; + uint64_t packet_di8_no_complete = 0; + uint64_t packet_di8_no_standard = 0; + uint64_t packet_di8_both_high = 0; + uint64_t packet_di8_both_normal = 0; + uint64_t packet_di8_mixed = 0; + + bool capture_started = false; + bool stop_loop_requested = false; + bool packet_active = false; + bool trigger_level_initialized = false; + bool trigger_prev_level = false; + std::size_t total_zeroed_samples = 0; + uint32_t next_lch = 0; + bool di1_initialized = false; + bool di1_prev_level = false; + const TickMs start_wait_deadline = tick_count_ms() + cfg.start_wait_ms; + const TickMs capture_loop_start = tick_count_ms(); + TickMs stats_window_start = capture_loop_start; + TickMs last_stats_print = capture_loop_start; + TickMs last_live_update = 0; + + uint64_t total_raw_words = 0; + uint64_t total_adc_samples = 0; + uint64_t total_din_samples = 0; + uint64_t total_stored_adc_samples = 0; + uint64_t total_completed_frames = 0; + uint64_t total_completed_packets = 0; + + uint64_t stats_raw_words = 0; + uint64_t stats_adc_samples = 0; + uint64_t stats_din_samples = 0; + uint64_t stats_stored_adc_samples = 0; + uint64_t stats_zeroed_samples = 0; + uint64_t stats_completed_frames = 0; + uint64_t stats_completed_packets = 0; + TtyProtocolWriter::StatsSnapshot tty_stats_window_start {}; + bool tty_overflow_warning_printed = false; + + auto print_stats = [&](bool final_report) { + const TickMs now = tick_count_ms(); + const double elapsed_s = std::max(1e-9, static_cast(now - stats_window_start) / 1000.0); + const double mb_per_s = (static_cast(stats_raw_words) * sizeof(uint32_t)) / elapsed_s / 1000.0 / 1000.0; + const double adc_samples_per_s = static_cast(stats_adc_samples) / elapsed_s; + const double din_samples_per_s = static_cast(stats_din_samples) / elapsed_s; + const double frames_per_ch_per_s = static_cast(stats_completed_frames) / elapsed_s; + const double packets_per_s = static_cast(stats_completed_packets) / elapsed_s; + const double zeroed_fraction = (stats_stored_adc_samples == 0U) + ? 0.0 + : (100.0 * static_cast(stats_zeroed_samples) / static_cast(stats_stored_adc_samples)); + const TtyProtocolWriter::StatsSnapshot tty_stats_now = + tty_writer ? tty_writer->stats() : TtyProtocolWriter::StatsSnapshot {}; + const uint64_t tty_frames_written = tty_stats_now.frames_written - tty_stats_window_start.frames_written; + const uint64_t tty_frames_dropped = tty_stats_now.frames_dropped - tty_stats_window_start.frames_dropped; + const uint64_t tty_ring_overflows = tty_stats_now.ring_overflows - tty_stats_window_start.ring_overflows; + + std::cout << std::fixed << std::setprecision(3) + << (final_report ? "Final stats: " : "Online stats: ") + << "MB/s=" << mb_per_s + << ", ADC samples/s=" << adc_samples_per_s + << ", DIN samples/s=" << din_samples_per_s + << ", frames/s per channel=" << frames_per_ch_per_s + << ", packets/s=" << packets_per_s; + if (!fast_tty_avg_stream_mode) { + if (cfg.di1_mode == Di1Mode::ZeroOnChange) { + std::cout << ", zeroed on DI1 change=" << zeroed_fraction << "% (" + << stats_zeroed_samples << "/" << stats_stored_adc_samples << ")"; + } else if (cfg.di1_mode == Di1Mode::Trace) { + std::cout << ", DI1 trace=enabled"; + } + } + if (tty_writer) { + std::cout << ", tty_frames_written=" << tty_frames_written + << ", tty_frames_dropped=" << tty_frames_dropped + << ", tty_ring_overflows=" << tty_ring_overflows; + } + std::cout << "\n"; + + if (!final_report) { + stats_window_start = now; + last_stats_print = now; + stats_raw_words = 0; + stats_adc_samples = 0; + stats_din_samples = 0; + stats_stored_adc_samples = 0; + stats_zeroed_samples = 0; + stats_completed_frames = 0; + stats_completed_packets = 0; + tty_stats_window_start = tty_stats_now; + } + }; + + auto append_tty_frame = [&](uint16_t word0, uint16_t word1, uint16_t word2, uint16_t word3) { + tty_frame_words.push_back(word0); + tty_frame_words.push_back(word1); + tty_frame_words.push_back(word2); + tty_frame_words.push_back(word3); + }; + + auto append_tty_packet_start = [&]() { + append_tty_frame(tty_packet_start_marker, 0xFFFF, 0xFFFF, 0xFFFF); + }; + + auto append_tty_group_step = [&]() { + if (!tty_di1_group_average || !tty_group_state.has_complete_step(cfg.channel_count)) { + return; + } + if (tty_group_state.next_index >= 0xFFFFU) { + fail("TTY protocol step index overflow within packet"); + } + + const double ch1_avg = tty_group_state.sum_ch1 / static_cast(tty_group_state.count_ch1); + const double ch2_avg = + (cfg.channel_count <= 1U) + ? 0.0 + : (tty_group_state.sum_ch2 / static_cast(tty_group_state.count_ch2)); + const uint16_t step_idx = static_cast(tty_group_state.next_index); + append_tty_frame( (cfg.profile == CaptureProfile::Amplitude) ? 0x001AU : 0x000AU, + step_idx, + static_cast(pack_raw_code_to_int16(ch1_avg)), + static_cast(pack_raw_code_to_int16(ch2_avg))); + ++tty_group_state.next_index; + ++packet_avg_steps; + if (cfg.do8_freq_ref) { + tty_di8_state.last_standard_index = step_idx; + tty_di8_state.last_standard_valid = true; + } + }; + + auto append_tty_do1_raw_marked_step = [&]() { + if (!tty_do1_raw_tty_marked) { + return; + } + if (!tty_do1_raw_state.step_level_initialized) { + return; + } + if (!tty_do1_raw_state.has_complete_step(cfg.channel_count)) { + tty_do1_raw_state.clear_step(); + return; + } + if (tty_do1_raw_state.next_index >= 0xFFFFU) { + fail("TTY protocol step index overflow within packet"); + } + + const double ch1_avg = tty_do1_raw_state.step_average(0U); + const double ch2_avg = (cfg.channel_count <= 1U) ? 0.0 : tty_do1_raw_state.step_average(1U); + const uint16_t marker = tty_do1_raw_state.step_di1_high ? 0x00A4U : 0x00A3U; + const uint16_t step_idx = tty_do1_raw_state.next_index; + append_tty_frame(marker, + step_idx, + static_cast(pack_raw_code_to_int16(ch1_avg)), + static_cast(pack_raw_code_to_int16(ch2_avg))); + ++tty_do1_raw_state.next_index; + ++packet_avg_steps; + tty_do1_raw_state.clear_step(); + if (cfg.do8_freq_ref) { + tty_di8_state.last_standard_index = step_idx; + tty_di8_state.last_standard_valid = true; + } + }; + + auto append_tty_do1_subtracted_step = [&]() { + if (!tty_do1_noise_subtract) { + return; + } + + if (!tty_do1_noise_state.step_level_initialized) { + return; + } + + if (!tty_do1_noise_state.has_complete_step(cfg.channel_count)) { + if (!tty_do1_noise_state.step_di1_high && + tty_do1_noise_state.has_pending_high(cfg.channel_count)) { + // Invalidate the HIGH->LOW pair if LOW step is incomplete. + tty_do1_noise_state.consume_pending_high(); + } + tty_do1_noise_state.finish_step(); + return; + } + + const double ch1_avg = tty_do1_noise_state.step_average(0U); + const double ch2_avg = (cfg.channel_count <= 1U) ? 0.0 : tty_do1_noise_state.step_average(1U); + + if (tty_do1_noise_state.step_di1_high) { + tty_do1_noise_state.update_last_high(0U, ch1_avg); + if (cfg.channel_count > 1U) { + tty_do1_noise_state.update_last_high(1U, ch2_avg); + } + tty_do1_noise_state.mark_high_pending(); + tty_do1_noise_state.finish_step(); + return; + } + + if (!tty_do1_noise_state.has_pending_high(cfg.channel_count)) { + // Skip LOW output unless a preceding HIGH step baseline is pending. + tty_do1_noise_state.finish_step(); + return; + } + + if (tty_do1_noise_state.next_index >= 0xFFFFU) { + fail("TTY protocol step index overflow within packet"); + } + + const double ch1_corrected = ch1_avg - tty_do1_noise_state.last_high(0U); + const double ch2_corrected = + (cfg.channel_count <= 1U) ? 0.0 : (ch2_avg - tty_do1_noise_state.last_high(1U)); + + const uint16_t step_idx = tty_do1_noise_state.next_index; + append_tty_frame((cfg.profile == CaptureProfile::Amplitude) ? 0x001AU : 0x000AU, + step_idx, + static_cast(pack_raw_code_to_int16(ch1_corrected)), + static_cast(pack_raw_code_to_int16(ch2_corrected))); + ++tty_do1_noise_state.next_index; + ++packet_avg_steps; + tty_do1_noise_state.consume_pending_high(); + tty_do1_noise_state.finish_step(); + if (cfg.do8_freq_ref) { + tty_di8_state.last_standard_index = step_idx; + tty_di8_state.last_standard_valid = true; + } + }; + + auto append_tty_do1_pair_subtracted_avg_run = [&]() { + if (!tty_do1_pair_subtract_avg) { + return; + } + if (!tty_do1_pair_state.run_initialized) { + return; + } + + if (!tty_do1_pair_state.has_output()) { + tty_do1_pair_state.clear_run(); + return; + } + + if (tty_do1_pair_state.next_index >= 0xFFFFU) { + fail("TTY protocol step index overflow within packet"); + } + + const uint16_t step_idx = tty_do1_pair_state.next_index; + append_tty_frame(0x000AU, + step_idx, + static_cast(pack_raw_code_to_int16(tty_do1_pair_state.avg_diff_ch1())), + static_cast(pack_raw_code_to_int16(tty_do1_pair_state.avg_diff_ch2()))); + ++tty_do1_pair_state.next_index; + ++packet_avg_steps; + tty_do1_pair_state.clear_run(); + if (cfg.do8_freq_ref) { + tty_di8_state.last_standard_index = step_idx; + tty_di8_state.last_standard_valid = true; + } + }; + + auto append_tty_di8_ref_step = [&]() { + if (!cfg.do8_freq_ref) { + return; + } + if (!tty_di8_state.has_complete_step(cfg.channel_count)) { + ++packet_di8_no_complete; + tty_di8_state.clear_step(); + return; + } + + const double ch1_avg = tty_di8_state.step_average(0U); + const double ch2_avg = (cfg.channel_count <= 1U) ? 0.0 : tty_di8_state.step_average(1U); + + bool exceeds = true; + if (cfg.do8_amplitude_threshold.has_value()) { + const double t = *cfg.do8_amplitude_threshold; + exceeds = (std::abs(ch1_avg) > t) || + ((cfg.channel_count > 1U) && (std::abs(ch2_avg) > t)); + } + + if (!exceeds) { + tty_di8_state.clear_step(); + return; + } + + if (!tty_di8_state.last_standard_valid) { + ++packet_di8_no_standard; + tty_di8_state.clear_step(); + return; + } + + append_tty_frame(kTtyMarkerDi8High, + tty_di8_state.last_standard_index, + static_cast(pack_raw_code_to_int16(ch1_avg)), + static_cast(pack_raw_code_to_int16(ch2_avg))); + ++packet_di8_ref_frames_emitted; + tty_di8_state.clear_step(); + }; + + auto flush_tty_frames = [&]() { + if (!tty_writer || tty_frame_words.empty()) { + return; + } + + tty_writer->enqueue_encoded_frames(tty_frame_words.data(), tty_frame_words.size() / 4U); + tty_frame_words.clear(); + + const auto tty_stats = tty_writer->stats(); + if (!tty_overflow_warning_printed && (tty_stats.ring_overflows != 0U)) { + std::cerr << "Warning: TTY ring buffer overflowed; dropping oldest frames to keep the stream continuous\n"; + tty_overflow_warning_printed = true; + } + }; + + auto start_packet = [&]() { + if (packet_active) { + return; + } + packet_avg_steps = 0; + fast_packet_frames = 0; + packet_clock_count = 0; + packet_di2_high_clocks = 0; + packet_di2_low_clocks = 0; + packet_di8_high_clocks = 0; + packet_di8_low_clocks = 0; + packet_di8_samples_accumulated = 0; + packet_di8_ref_frames_emitted = 0; + packet_di8_no_complete = 0; + packet_di8_no_standard = 0; + packet_di8_both_high = 0; + packet_di8_both_normal = 0; + packet_di8_mixed = 0; + if (!fast_tty_avg_stream_mode) { + current_packet.reset(target_frames, cfg.channel_count); + } + if (tty_di1_group_average) { + tty_group_state.reset_packet(); + append_tty_packet_start(); + } else if (tty_do1_raw_tty_marked) { + tty_do1_raw_state.reset_packet(); + append_tty_packet_start(); + } else if (tty_do1_noise_subtract) { + tty_do1_noise_state.reset_packet(); + append_tty_packet_start(); + } else if (tty_do1_pair_subtract_avg) { + tty_do1_pair_state.reset_packet(); + append_tty_packet_start(); + } + if (cfg.do8_freq_ref) { + tty_di8_state.reset_packet(); + } + packet_active = true; + }; + + auto finish_packet = [&](PacketCloseReason reason) { + if (packet_clock_count != (packet_di2_high_clocks + packet_di2_low_clocks)) { + std::ostringstream message; + message << "DI2 clock split invariant failed: clocks=" << packet_clock_count + << ", high=" << packet_di2_high_clocks + << ", low=" << packet_di2_low_clocks; + fail(message.str()); + } + + if (tty_di1_group_average) { + append_tty_group_step(); + if (cfg.do8_freq_ref) { append_tty_di8_ref_step(); } + tty_group_state.clear_step(); + } else if (tty_do1_raw_tty_marked) { + append_tty_do1_raw_marked_step(); + if (cfg.do8_freq_ref) { append_tty_di8_ref_step(); } + tty_do1_raw_state.clear_step(); + } else if (tty_do1_noise_subtract) { + // Finalize the last DI1 run in packet, drop only incomplete channel tuples. + append_tty_do1_subtracted_step(); + if (cfg.do8_freq_ref) { append_tty_di8_ref_step(); } + tty_do1_noise_state.clear_step(); + } else if (tty_do1_pair_subtract_avg) { + append_tty_do1_pair_subtracted_avg_run(); + if (cfg.do8_freq_ref) { append_tty_di8_ref_step(); } + tty_do1_pair_state.reset_packet(); + } + if (cfg.do8_freq_ref) { + tty_di8_state.clear_step(); + } + + const std::size_t frames = fast_tty_avg_stream_mode + ? fast_packet_frames + : current_packet.frame_count(cfg.channel_count); + if (frames != 0U) { + const double packet_duration_ms = (1000.0 * static_cast(frames)) / actual_frame_freq_hz; + if (fast_tty_avg_stream_mode) { + const std::size_t packet_index = static_cast(total_completed_packets + 1U); + std::cout << std::fixed << std::setprecision(3) + << "Packet " << packet_index + << ": frames/ch=" << frames + << ", duration_ms=" << packet_duration_ms + << ", close_reason=" << packet_close_reason_to_string(reason) + << ", clocks=" << packet_clock_count + << ", di2_high_clocks=" << packet_di2_high_clocks + << ", di2_low_clocks=" << packet_di2_low_clocks; + if (cfg.do8_freq_ref) { + std::cout << ", di8_high_clocks=" << packet_di8_high_clocks + << ", di8_low_clocks=" << packet_di8_low_clocks + << ", di8_pairs=" << packet_di8_samples_accumulated + << ", di8_ref_frames=" << packet_di8_ref_frames_emitted + << ", di8_no_complete=" << packet_di8_no_complete + << ", di8_no_std=" << packet_di8_no_standard + << ", di8_hi=" << packet_di8_both_high + << ", di8_lo=" << packet_di8_both_normal + << ", di8_mix=" << packet_di8_mixed; + } + std::cout << ", avg_steps=" << packet_avg_steps + << "\n"; + } else { + current_packet.channels[0].resize(frames); + if (cfg.channel_count >= 2U) { + current_packet.channels[1].resize(frames); + } else { + current_packet.channels[1].clear(); + } + if (cfg.di1_mode == Di1Mode::Trace) { + current_packet.di1.resize(frames); + } else { + current_packet.di1.clear(); + } + + CapturePacket packet; + packet.packet_index = static_cast(total_completed_packets + 1U); + packet.channel_count = cfg.channel_count; + packet.has_di1_trace = (cfg.di1_mode == Di1Mode::Trace); + packet.ch1 = std::move(current_packet.channels[0]); + packet.ch2 = std::move(current_packet.channels[1]); + packet.di1 = std::move(current_packet.di1); + + const double zeroed_fraction = (current_packet.stored_samples == 0U) + ? 0.0 + : (100.0 * static_cast(current_packet.zeroed_samples) / + static_cast(current_packet.stored_samples)); + + std::cout << std::fixed << std::setprecision(3) + << "Packet " << packet.packet_index + << ": frames/ch=" << frames + << ", duration_ms=" << packet_duration_ms + << ", close_reason=" << packet_close_reason_to_string(reason) + << ", clocks=" << packet_clock_count + << ", di2_high_clocks=" << packet_di2_high_clocks + << ", di2_low_clocks=" << packet_di2_low_clocks; + if (cfg.do8_freq_ref) { + std::cout << ", di8_high_clocks=" << packet_di8_high_clocks + << ", di8_low_clocks=" << packet_di8_low_clocks; + } + if (cfg.di1_mode == Di1Mode::ZeroOnChange) { + std::cout << ", zeroed_on_DI1_change=" << zeroed_fraction << "% (" + << current_packet.zeroed_samples << "/" << current_packet.stored_samples << ")"; + } else if (cfg.di1_mode == Di1Mode::Trace) { + std::cout << ", di1_trace_frames=" << packet.di1.size(); + } + std::cout << "\n"; + + writer->append_csv_packet(packet, actual_frame_freq_hz, csv_global_frame_index); + packets.push_back(std::move(packet)); + if ((cfg.svg_history_packets != 0U) && (packets.size() > cfg.svg_history_packets)) { + packets.pop_front(); + } + + const double elapsed_capture_s = + std::max(1e-9, static_cast(tick_count_ms() - capture_loop_start) / 1000.0); + const double packets_per_second = + (elapsed_capture_s >= 0.1) + ? (static_cast(total_completed_packets + 1U) / elapsed_capture_s) + : 0.0; + const TickMs now = tick_count_ms(); + const bool should_update_live = + (cfg.live_update_period_ms == 0U) || + (last_live_update == 0U) || + ((now - last_live_update) >= cfg.live_update_period_ms); + if (should_update_live) { + writer->update_live_plot(packets.back(), + static_cast(total_completed_packets + 1U), + packets_per_second, + actual_frame_freq_hz, + packet_close_reason_to_string(reason), + current_packet.zeroed_samples, + current_packet.stored_samples); + last_live_update = now; + } + } + + ++total_completed_packets; + ++stats_completed_packets; + + const double elapsed_capture_s = + std::max(1e-9, static_cast(tick_count_ms() - capture_loop_start) / 1000.0); + const double packets_per_second = + (elapsed_capture_s >= 0.1) + ? (static_cast(total_completed_packets) / elapsed_capture_s) + : 0.0; + std::cout << std::fixed << std::setprecision(3) + << " packets/s(avg)=" << packets_per_second; + if (elapsed_capture_s < 0.1) { + std::cout << " (warming up)"; + } + std::cout << "\n"; + } + + packet_active = false; + packet_avg_steps = 0; + fast_packet_frames = 0; + packet_clock_count = 0; + packet_di2_high_clocks = 0; + packet_di2_low_clocks = 0; + packet_di8_high_clocks = 0; + packet_di8_low_clocks = 0; + packet_di8_samples_accumulated = 0; + packet_di8_ref_frames_emitted = 0; + packet_di8_no_complete = 0; + packet_di8_no_standard = 0; + packet_di8_both_high = 0; + packet_di8_both_normal = 0; + packet_di8_mixed = 0; + if (!fast_tty_avg_stream_mode) { + current_packet.reset(target_frames, cfg.channel_count); + } + if (tty_di1_group_average) { + tty_group_state.clear_step(); + } else if (tty_do1_raw_tty_marked) { + tty_do1_raw_state.clear_step(); + } else if (tty_do1_noise_subtract) { + tty_do1_noise_state.clear_step(); + } else if (tty_do1_pair_subtract_avg) { + tty_do1_pair_state.reset_packet(); + } + }; + + while (!stop_loop_requested) { + if (tty_writer) { + tty_writer->throw_if_failed(); + } + if ((cfg.packet_limit != 0U) && (total_completed_packets >= cfg.packet_limit)) { + stop_loop_requested = true; + break; + } + if (console_stop_requested()) { + if (packet_active) { + finish_packet(PacketCloseReason::UserStop); + flush_tty_frames(); + } + stop_loop_requested = true; + break; + } + + 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(ready_words, read_capacity_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)); + } + if (recvd > 0) { + total_raw_words += static_cast(recvd); + stats_raw_words += static_cast(recvd); + } + if (recvd == 0) { + if (!capture_started && (tick_count_ms() >= start_wait_deadline)) { + std::ostringstream message; + message << "Timeout before first ADC data. start=" + << sync_mode_to_string(cfg.sync_start_mode, true) + << ", clock=" << sync_mode_to_string(cfg.sync_mode, false) << ". "; + + if (cfg.sync_start_mode == X502_SYNC_EXTERNAL_MASTER) { + message << "With start:start_in the module waits for START_IN after StreamsStart(); " + "until that condition occurs the external clock is ignored. "; + } else if (cfg.sync_start_mode == X502_SYNC_INTERNAL) { + message << "Start is immediate, so this usually means there is no valid external clock on the selected clock line. "; + } else { + message << "This usually means the selected start condition did not occur after StreamsStart(), " + "or no valid external clock arrived afterwards. "; + } + + message << "For a quick clock-only check, try start:immediate. " + "If you use a separate start pulse, it must arrive after the program starts waiting. " + "You can also increase start_wait_ms."; + fail(message.str()); + } + continue; + } + + uint32_t adc_count = fast_tty_avg_stream_mode ? static_cast(adc_raw_buffer.size()) + : static_cast(adc_buffer.size()); + uint32_t din_count = static_cast(din_buffer.size()); + const uint32_t process_flags = + fast_tty_avg_stream_mode ? 0U : static_cast(X502_PROC_FLAGS_VOLT); + const int32_t process_err = api.ProcessData(device.hnd, + raw.data(), + static_cast(recvd), + process_flags, + fast_tty_avg_stream_mode ? adc_raw_buffer.data() : adc_buffer.data(), + &adc_count, + din_buffer.data(), + &din_count); + if (process_err == X502_ERR_STREAM_OVERFLOW) { + std::ostringstream message; + message << "Process ADC+DIN data: " << x502_error(api, process_err) + << ". Current ADC rate=" << actual_sample_clock_hz + << " Hz, DIN rate=" << actual_din_freq_hz + << " Hz, combined input=" << combined_input_rate_hz + << " words/s. Try larger recv_block/buffer_words/step_words, a longer " + << "live_update_period_ms, or a lower sample_clock_hz / DIN load."; + fail(message.str()); + } + expect_ok(api, process_err, "Process ADC+DIN data"); + + uint32_t raw_adc_count = fast_tty_avg_stream_mode ? adc_count : 0U; + if (tty_writer && !fast_tty_avg_stream_mode) { + for (std::size_t i = 0; i < static_cast(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 parsing returned a different sample count than voltage processing"); + } + + if (!tty_di1_group_average) { + tty_frame_words.clear(); + const std::size_t tty_frame_count = + (cfg.channel_count <= 1U) + ? static_cast(raw_adc_count) + : ((static_cast(raw_adc_count) + 1U) / 2U); + tty_frame_words.reserve(tty_frame_count * 4U); + for (uint32_t i = 0; i < raw_adc_count; ++i) { + const int16_t sample = pack_raw_code_to_int16(adc_raw_buffer[i]); + if (cfg.channel_count <= 1U) { + append_tty_frame( (cfg.profile == CaptureProfile::Amplitude) ? 0x001AU : 0x000AU, + tty_state.next_index, + static_cast(sample), + 0U); + if (tty_state.next_index >= 0xFFFEU) { + tty_state.next_index = 1U; + } else { + ++tty_state.next_index; + } + continue; + } + if (!tty_state.pending_ch1_valid) { + tty_state.pending_ch1 = sample; + tty_state.pending_ch1_valid = true; + continue; + } + + append_tty_frame( (cfg.profile == CaptureProfile::Amplitude) ? 0x001AU : 0x000AU, + tty_state.next_index, + static_cast(tty_state.pending_ch1), + static_cast(sample)); + tty_state.pending_ch1_valid = false; + + if (tty_state.next_index >= 0xFFFEU) { + tty_state.next_index = 1U; + } else { + ++tty_state.next_index; + } + } + flush_tty_frames(); + } + } + + if ((adc_count == 0U) && (din_count == 0U) && (!tty_writer || (raw_adc_count == 0U))) { + continue; + } + + capture_started = capture_started || (adc_count != 0U); + total_adc_samples += adc_count; + total_din_samples += din_count; + stats_adc_samples += adc_count; + stats_din_samples += din_count; + if (fast_tty_avg_stream_mode) { + for (uint32_t i = 0; i < raw_adc_count; ++i) { + pending_adc_raw.push_back(adc_raw_buffer[i]); + } + } else { + for (uint32_t i = 0; i < adc_count; ++i) { + pending_adc.push_back(adc_buffer[i]); + } + } + for (uint32_t i = 0; i < din_count; ++i) { + pending_din.push_back(din_buffer[i]); + } + + if (((!fast_tty_avg_stream_mode) && (pending_adc.size() > 1000000U)) || + (pending_din.size() > 1000000U) || + (fast_tty_avg_stream_mode && (pending_adc_raw.size() > 1000000U))) { + fail("Internal backlog grew too large while aligning ADC and DIN samples"); + } + + while ((fast_tty_avg_stream_mode ? !pending_adc_raw.empty() : !pending_adc.empty()) && + !pending_din.empty() && + !stop_loop_requested) { + const double adc_value = fast_tty_avg_stream_mode ? 0.0 : pending_adc.front(); + if (!fast_tty_avg_stream_mode) { + pending_adc.pop_front(); + } + const double adc_raw_value = fast_tty_avg_stream_mode ? pending_adc_raw.front() : 0.0; + if (fast_tty_avg_stream_mode) { + pending_adc_raw.pop_front(); + } + + const uint32_t din_value = pending_din.front(); + pending_din.pop_front(); + // Keep ADC logical-channel phase aligned even when samples are skipped outside packet windows. + const uint32_t lch = next_lch; + next_lch = (next_lch + 1U) % cfg.channel_count; + + const bool di1_level = (din_value & kE502Digital1Mask) != 0U; + bool di1_changed = false; + if (!di1_initialized) { + di1_prev_level = di1_level; + di1_initialized = true; + } else if (di1_level != di1_prev_level) { + di1_changed = true; + di1_prev_level = di1_level; + } + + const bool di_syn2_level = (din_value & kE502DiSyn2Mask) != 0U; + bool start_edge = false; + bool stop_edge = false; + if (!trigger_level_initialized) { + trigger_prev_level = di_syn2_level; + trigger_level_initialized = true; + if (!packet_active) { + if (cfg.sync_start_mode == X502_SYNC_INTERNAL) { + start_packet(); + } else if (sync_uses_di_syn2(cfg.sync_start_mode) && di_syn2_level) { + start_packet(); + } else if (!sync_uses_di_syn2(cfg.sync_start_mode)) { + start_packet(); + } + } + } else { + start_edge = matches_sync_edge(cfg.sync_start_mode, trigger_prev_level, di_syn2_level); + if (cfg.stop_mode != StopMode::TargetFrames) { + stop_edge = matches_stop_edge(cfg.stop_mode, trigger_prev_level, di_syn2_level); + } + trigger_prev_level = di_syn2_level; + } + if (!packet_active && (cfg.sync_start_mode == X502_SYNC_INTERNAL)) { + start_packet(); + } + + 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(); + } + + if (packet_active && stop_edge) { + finish_packet(PacketCloseReason::ExternalStopEdge); + if ((cfg.packet_limit != 0U) && (total_completed_packets >= cfg.packet_limit)) { + stop_loop_requested = true; + } + continue; + } + + if (!packet_active) { + continue; + } + + const bool di2_level = (din_value & kE502Digital2Mask) != 0U; + if (di2_level) { + ++packet_di2_high_clocks; + } else { + ++packet_di2_low_clocks; + } + ++packet_clock_count; + + if (cfg.do8_freq_ref) { + const bool di8_raw = (din_value & kE502Digital8Mask) != 0U; + if (di8_raw) { + ++packet_di8_high_clocks; + } else { + ++packet_di8_low_clocks; + } + } + + const bool di8_level = cfg.do8_freq_ref + ? ((din_value & kE502Digital8Mask) != 0U) + : false; + const bool di8_settled_high = cfg.do8_freq_ref + ? tty_di8_state.update_di8(di8_level) + : false; + const bool di8_in_guard = cfg.do8_freq_ref && tty_di8_state.in_guard; + + if (tty_di1_group_average && di1_changed) { + append_tty_group_step(); + if (cfg.do8_freq_ref) { append_tty_di8_ref_step(); tty_di8_state.clear_step(); } + tty_group_state.clear_step(); + } else if (tty_do1_raw_tty_marked) { + if (!tty_do1_raw_state.step_level_initialized) { + tty_do1_raw_state.start_new_step(di1_level); + } else if (di1_changed) { + append_tty_do1_raw_marked_step(); + if (cfg.do8_freq_ref) { append_tty_di8_ref_step(); tty_di8_state.clear_step(); } + tty_do1_raw_state.start_new_step(di1_level); + } + } else if (tty_do1_noise_subtract) { + if (!tty_do1_noise_state.step_level_initialized) { + tty_do1_noise_state.start_new_step(di1_level); + } else if (di1_changed) { + append_tty_do1_subtracted_step(); + if (cfg.do8_freq_ref) { append_tty_di8_ref_step(); tty_di8_state.clear_step(); } + tty_do1_noise_state.start_new_step(di1_level); + } + } + + if (!fast_tty_avg_stream_mode && + (cfg.di1_mode == Di1Mode::Trace) && + ((cfg.channel_count <= 1U) || (lch == 0U))) { + current_packet.pending_frame_di1 = static_cast(di1_level ? 1U : 0U); + current_packet.pending_frame_di1_valid = true; + } + + if (fast_tty_avg_stream_mode) { + if (fast_packet_frames < target_frames) { + if (tty_do1_pair_subtract_avg) { + if (lch == 0U) { + tty_do1_pair_state.add_pending_ch1(adc_raw_value, di1_level, di2_level, + di8_level); + } else if (lch == 1U) { + if (tty_do1_pair_state.pending_ch1_valid) { + const bool pair_di1_matches = + (tty_do1_pair_state.pending_ch1_di1_high == di1_level); + const bool pair_di2_matches = + (tty_do1_pair_state.pending_ch1_di2_high == di2_level); + // Use raw DI8 level for pair matching (not guard-filtered). + // Guard-based filtering consumed ALL settled HIGH samples + // due to per-DIN-sample guard counting vs per-DOUT-tick transitions. + // Mixed-level pairs (transition boundaries) are still discarded. + const bool both_di8_high = + tty_do1_pair_state.pending_ch1_di8_high && di8_level; + const bool both_di8_normal = + !tty_do1_pair_state.pending_ch1_di8_high && !di8_level; + + if (pair_di1_matches && pair_di2_matches) { + if (both_di8_high) { + tty_di8_state.add_sample(0U, tty_do1_pair_state.pending_ch1_raw); + tty_di8_state.add_sample(1U, adc_raw_value); + ++packet_di8_samples_accumulated; + ++packet_di8_both_high; + } else if (both_di8_normal) { + ++packet_di8_both_normal; + if (!tty_do1_pair_state.run_initialized) { + tty_do1_pair_state.start_run(di1_level); + } else if (tty_do1_pair_state.run_di1_high != di1_level) { + append_tty_do1_pair_subtracted_avg_run(); + if (cfg.do8_freq_ref) { append_tty_di8_ref_step(); tty_di8_state.clear_step(); } + tty_do1_pair_state.start_run(di1_level); + } + + tty_do1_pair_state.add_pair_sample(di2_level, + tty_do1_pair_state.pending_ch1_raw, + adc_raw_value); + } else { + ++packet_di8_mixed; // mixed DI8 pair -- discard + } + } + } + tty_do1_pair_state.clear_pending_ch1(); + } else { + tty_do1_pair_state.clear_pending_ch1(); + } + } else if (di8_settled_high) { + tty_di8_state.add_sample(lch, adc_raw_value); + } else if (!di8_in_guard) { + if (tty_do1_noise_subtract) { + tty_do1_noise_state.add_sample(lch, adc_raw_value); + } else if (tty_do1_raw_tty_marked) { + tty_do1_raw_state.add_sample(lch, adc_raw_value); + } else { + tty_group_state.add_sample(lch, adc_raw_value); + } + } + if (lch == (cfg.channel_count - 1U)) { + ++fast_packet_frames; + ++total_completed_frames; + ++stats_completed_frames; + } + } + } else { + double stored_value = adc_value; + if ((cfg.di1_mode == Di1Mode::ZeroOnChange) && di1_changed) { + stored_value = 0.0; + ++total_zeroed_samples; + ++stats_zeroed_samples; + ++current_packet.zeroed_samples; + } + + if (current_packet.channels[lch].size() < target_frames) { + current_packet.channels[lch].push_back(stored_value); + ++current_packet.stored_samples; + ++total_stored_adc_samples; + ++stats_stored_adc_samples; + if (lch == (cfg.channel_count - 1U)) { + if ((cfg.di1_mode == Di1Mode::Trace) && + current_packet.pending_frame_di1_valid && + (current_packet.di1.size() < target_frames)) { + current_packet.di1.push_back(current_packet.pending_frame_di1); + current_packet.pending_frame_di1_valid = false; + } + ++total_completed_frames; + ++stats_completed_frames; + } + } + } + + const std::size_t completed_frames = fast_tty_avg_stream_mode + ? fast_packet_frames + : current_packet.frame_count(cfg.channel_count); + if (completed_frames >= target_frames) { + finish_packet(PacketCloseReason::DurationLimit); + if ((cfg.packet_limit != 0U) && (total_completed_packets >= cfg.packet_limit)) { + stop_loop_requested = true; + } + } + } + + flush_tty_frames(); + + if (console_stop_requested()) { + if (packet_active) { + finish_packet(PacketCloseReason::UserStop); + } + flush_tty_frames(); + stop_loop_requested = true; + } + + if ((cfg.stats_period_ms != 0U) && ((tick_count_ms() - last_stats_print) >= cfg.stats_period_ms)) { + print_stats(false); + } + } + + if (cfg.do1_toggle_per_frame) { + expect_ok(api, api.OutCycleStop(device.hnd, X502_OUT_CYCLE_FLAGS_WAIT_DONE), "Stop cyclic DOUT pattern"); + } + + expect_ok(api, api.StreamsStop(device.hnd), "Stop streams"); + device.streams_started = false; + if (cfg.do1_toggle_per_frame) { + const uint32_t clear_mask = kE502Do1Mask | kE502Do2Mask | (cfg.do8_freq_ref ? kE502Do8Mask : 0U); + expect_ok(api, + api.AsyncOutDig(device.hnd, kE502Do2Mask, ~clear_mask), + "Force DO1/DO8 LOW and keep DO2 HIGH after cyclic DOUT stop"); + } else { + expect_ok(api, api.AsyncOutDig(device.hnd, kE502Do2Mask, ~kE502Do2Mask), "Keep DO2 HIGH after streams stop"); + } + + if ((cfg.stats_period_ms != 0U) && ((stats_raw_words != 0U) || (stats_adc_samples != 0U) || + (stats_din_samples != 0U) || (stats_stored_adc_samples != 0U) || (stats_zeroed_samples != 0U) || + (stats_completed_frames != 0U))) { + print_stats(false); + } + + TtyProtocolWriter::StatsSnapshot tty_final_stats {}; + if (tty_writer) { + tty_writer->shutdown(); + tty_writer->throw_if_failed(); + tty_final_stats = tty_writer->stats(); + } + + if (!fast_tty_avg_stream_mode) { + std::cout << "Captured " << total_completed_packets << " packet(s) in capture mode\n"; + if (cfg.di1_mode == Di1Mode::ZeroOnChange) { + std::cout << "ADC samples forced to 0 on DI1 change: " << total_zeroed_samples << "\n"; + } else if (cfg.di1_mode == Di1Mode::Trace) { + std::cout << "DI1 synchronous trace exported to live plot\n"; + } + } else { + std::cout << "Captured " << total_completed_packets + << " packet(s) in tty fast stream-only mode (" << fast_tty_mode_name << ")\n"; + } + std::cout << "Average stats: " + << "MB/s=" << std::fixed << std::setprecision(3) + << ((static_cast(total_raw_words) * sizeof(uint32_t)) / + std::max(1e-9, static_cast(tick_count_ms() - capture_loop_start) / 1000.0) / + 1000.0 / 1000.0) + << ", ADC samples/s=" + << (static_cast(total_adc_samples) / + std::max(1e-9, static_cast(tick_count_ms() - capture_loop_start) / 1000.0)) + << ", DIN samples/s=" + << (static_cast(total_din_samples) / + std::max(1e-9, static_cast(tick_count_ms() - capture_loop_start) / 1000.0)) + << ", frames/s per channel=" + << (static_cast(total_completed_frames) / + std::max(1e-9, static_cast(tick_count_ms() - capture_loop_start) / 1000.0)) + << ", packets/s=" + << (static_cast(total_completed_packets) / + std::max(1e-9, static_cast(tick_count_ms() - capture_loop_start) / 1000.0)) + << ", packets captured=" << total_completed_packets; + if (!fast_tty_avg_stream_mode) { + if (cfg.di1_mode == Di1Mode::ZeroOnChange) { + std::cout << ", zeroed on DI1 change=" + << ((total_stored_adc_samples == 0U) + ? 0.0 + : (100.0 * static_cast(total_zeroed_samples) / static_cast(total_stored_adc_samples))) + << "% (" << total_zeroed_samples << "/" << total_stored_adc_samples << ")"; + } else if (cfg.di1_mode == Di1Mode::Trace) { + std::cout << ", DI1 trace=enabled"; + } + } + if (tty_writer) { + std::cout << ", tty_frames_written=" << tty_final_stats.frames_written + << ", tty_frames_dropped=" << tty_final_stats.frames_dropped + << ", tty_ring_overflows=" << tty_final_stats.ring_overflows; + } + if (fast_tty_avg_stream_mode) { + std::cout << ", tty fast stream-only mode=" << fast_tty_mode_name; + } + std::cout << "\n"; + + return 0; +} + +} // namespace + +int main(int argc, char** argv) { + try { + const Config cfg = parse_args(argc, argv); + return run(cfg); + } catch (const std::bad_alloc&) { + std::cerr << "Error: bad allocation. The process ran out of RAM. " + "Try a smaller duration_ms, fewer logical channels, or a lower svg_history_packets " + "(for example svg_history_packets:10 or svg_history_packets:0 only if you really need all packets in SVG).\n"; + return 1; + } catch (const std::exception& ex) { + std::cerr << "Error: " << ex.what() << "\n"; + return 1; + } +} diff --git a/data_acq_and_processing/kamil_adc_collector/src/tty_protocol_writer.cpp b/data_acq_and_processing/kamil_adc_collector/src/tty_protocol_writer.cpp new file mode 100644 index 0000000..dbca921 --- /dev/null +++ b/data_acq_and_processing/kamil_adc_collector/src/tty_protocol_writer.cpp @@ -0,0 +1,450 @@ +#include "tty_protocol_writer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +struct TtyProtocolWriter::Impl {}; + +TtyProtocolWriter::TtyProtocolWriter(std::string path, std::size_t ring_capacity_bytes) + : path_(std::move(path)) { + (void) ring_capacity_bytes; + throw std::runtime_error("tty output is supported only on Linux/POSIX"); +} + +TtyProtocolWriter::~TtyProtocolWriter() = default; + +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) index; + (void) ch1_avg; + (void) ch2_avg; +} + +void TtyProtocolWriter::enqueue_encoded_frames(const uint16_t* words, std::size_t frame_count) { + (void) words; + (void) frame_count; +} + +TtyProtocolWriter::StatsSnapshot TtyProtocolWriter::stats() const { + return {}; +} + +const std::string& TtyProtocolWriter::path() const { + return path_; +} + +void TtyProtocolWriter::throw_if_failed() const {} + +void TtyProtocolWriter::shutdown() {} + +void TtyProtocolWriter::enqueue_frame(uint16_t word0, uint16_t word1, uint16_t word2, uint16_t word3) { + (void) word0; + (void) word1; + (void) word2; + (void) word3; +} + +void TtyProtocolWriter::worker_loop() {} + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::size_t kFrameWordCount = 4U; +constexpr std::size_t kFrameByteCount = kFrameWordCount * sizeof(uint16_t); +constexpr std::size_t kWriteBatchFrames = 256U; + +using EncodedFrame = std::array; + +std::string io_error(const std::string& action, const std::string& path) { + std::ostringstream out; + out << action << " '" << path << "': " << std::strerror(errno); + return out.str(); +} + +void close_fd_if_open(int& fd) noexcept { + if (fd >= 0) { + ::close(fd); + fd = -1; + } +} + +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); + 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) { + 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 read_link_target(const std::string& path) { + std::array 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(len)] = '\0'; + return std::string(buf.data()); +} + +EncodedFrame encode_frame(uint16_t word0, uint16_t word1, uint16_t word2, uint16_t word3) { + const uint16_t words[kFrameWordCount] = {word0, word1, word2, word3}; + EncodedFrame frame {}; + std::memcpy(frame.data(), words, sizeof(words)); + return frame; +} + +} // namespace + +struct TtyProtocolWriter::Impl { + explicit Impl(std::size_t ring_capacity_bytes) + : capacity_frames(std::max(1U, ring_capacity_bytes / kFrameByteCount)), + ring(capacity_frames) {} + + int fd = -1; + int slave_fd = -1; + std::string slave_path; + bool owns_link = false; + + const std::size_t capacity_frames; + std::vector ring; + std::size_t head = 0; + std::size_t size = 0; + + mutable std::mutex mutex; + std::condition_variable data_ready_cv; + std::thread worker; + bool stop_requested = false; + std::exception_ptr failure; + StatsSnapshot stats; +}; + +TtyProtocolWriter::TtyProtocolWriter(std::string path, std::size_t ring_capacity_bytes) + : path_(std::move(path)), + impl_(std::make_unique(ring_capacity_bytes)) { + if (is_character_device_path(path_)) { + impl_->fd = ::open(path_.c_str(), O_WRONLY | O_NOCTTY); + if (impl_->fd < 0) { + throw std::runtime_error(io_error("Cannot open tty output", path_)); + } + } else { + std::array slave_name {}; + if (::openpty(&impl_->fd, &impl_->slave_fd, slave_name.data(), nullptr, nullptr) != 0) { + throw std::runtime_error(io_error("Cannot create PTY bridge for", path_)); + } + + try { + impl_->slave_path = slave_name.data(); + set_fd_raw(impl_->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(impl_->slave_path.c_str(), path_.c_str()) != 0) { + throw std::runtime_error(io_error("Cannot create tty symlink", path_)); + } + impl_->owns_link = true; + } catch (...) { + close_fd_if_open(impl_->slave_fd); + close_fd_if_open(impl_->fd); + throw; + } + } + + impl_->worker = std::thread([this]() { worker_loop(); }); +} + +TtyProtocolWriter::~TtyProtocolWriter() { + try { + shutdown(); + } catch (...) { + } + + if (!impl_) { + return; + } + + if (impl_->owns_link && !path_.empty()) { + try { + const auto target = read_link_target(path_); + if (target && (*target == impl_->slave_path)) { + ::unlink(path_.c_str()); + } + } catch (...) { + } + impl_->owns_link = false; + } + + close_fd_if_open(impl_->slave_fd); + close_fd_if_open(impl_->fd); +} + +void TtyProtocolWriter::emit_packet_start(uint16_t marker) { + enqueue_frame(marker, 0xFFFF, 0xFFFF, 0xFFFF); +} + +void TtyProtocolWriter::emit_step(uint16_t index, int16_t ch1_avg, int16_t ch2_avg) { + enqueue_frame(0x000A, + index, + static_cast(ch1_avg), + static_cast(ch2_avg)); +} + +void TtyProtocolWriter::enqueue_encoded_frames(const uint16_t* words, std::size_t frame_count) { + if ((frame_count == 0U) || (words == nullptr)) { + return; + } + + throw_if_failed(); + + std::lock_guard lock(impl_->mutex); + if (impl_->failure) { + std::rethrow_exception(impl_->failure); + } + if (impl_->stop_requested) { + throw std::runtime_error("tty writer is already shut down"); + } + + std::size_t start_frame = 0; + std::size_t frames_to_copy = frame_count; + std::size_t dropped_frames = 0; + + if (frame_count >= impl_->capacity_frames) { + start_frame = frame_count - impl_->capacity_frames; + frames_to_copy = impl_->capacity_frames; + dropped_frames = impl_->size + start_frame; + impl_->head = 0; + impl_->size = 0; + } else { + const std::size_t available_frames = impl_->capacity_frames - impl_->size; + if (frame_count > available_frames) { + dropped_frames = frame_count - available_frames; + impl_->head = (impl_->head + dropped_frames) % impl_->capacity_frames; + impl_->size -= dropped_frames; + } + } + + if (dropped_frames != 0U) { + impl_->stats.frames_dropped += static_cast(dropped_frames); + ++impl_->stats.ring_overflows; + } + + for (std::size_t i = 0; i < frames_to_copy; ++i) { + const std::size_t src_index = (start_frame + i) * kFrameWordCount; + const std::size_t dst_index = (impl_->head + impl_->size) % impl_->capacity_frames; + impl_->ring[dst_index] = encode_frame(words[src_index + 0U], + words[src_index + 1U], + words[src_index + 2U], + words[src_index + 3U]); + ++impl_->size; + } + + impl_->data_ready_cv.notify_one(); +} + +TtyProtocolWriter::StatsSnapshot TtyProtocolWriter::stats() const { + if (!impl_) { + return {}; + } + + std::lock_guard lock(impl_->mutex); + return impl_->stats; +} + +const std::string& TtyProtocolWriter::path() const { + return path_; +} + +void TtyProtocolWriter::throw_if_failed() const { + if (!impl_) { + return; + } + + std::exception_ptr failure; + { + std::lock_guard lock(impl_->mutex); + failure = impl_->failure; + } + + if (failure) { + std::rethrow_exception(failure); + } +} + +void TtyProtocolWriter::shutdown() { + if (!impl_) { + return; + } + + { + std::lock_guard lock(impl_->mutex); + impl_->stop_requested = true; + } + + close_fd_if_open(impl_->fd); + impl_->data_ready_cv.notify_all(); + + if (impl_->worker.joinable()) { + impl_->worker.join(); + } +} + +void TtyProtocolWriter::enqueue_frame(uint16_t word0, uint16_t word1, uint16_t word2, uint16_t word3) { + const uint16_t words[kFrameWordCount] = {word0, word1, word2, word3}; + enqueue_encoded_frames(words, 1U); +} + +void TtyProtocolWriter::worker_loop() { + for (;;) { + std::array write_batch; + std::size_t batch_count = 0; + { + std::unique_lock lock(impl_->mutex); + impl_->data_ready_cv.wait(lock, [this]() { + return impl_->stop_requested || impl_->failure || (impl_->size != 0U); + }); + + if (impl_->failure || impl_->stop_requested) { + return; + } + + batch_count = std::min(impl_->size, kWriteBatchFrames); + for (std::size_t i = 0; i < batch_count; ++i) { + const EncodedFrame& frame = impl_->ring[impl_->head]; + std::memcpy(write_batch.data() + (i * kFrameByteCount), frame.data(), kFrameByteCount); + impl_->head = (impl_->head + 1U) % impl_->capacity_frames; + --impl_->size; + } + } + + const std::uint8_t* bytes = write_batch.data(); + std::size_t remaining = batch_count * kFrameByteCount; + while (remaining != 0U) { + const ssize_t written = ::write(impl_->fd, bytes, remaining); + if (written < 0) { + if (errno == EINTR) { + continue; + } + if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EIO)) { + { + std::lock_guard lock(impl_->mutex); + if (impl_->stop_requested) { + return; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + continue; + } + + std::lock_guard lock(impl_->mutex); + if (!impl_->stop_requested) { + impl_->failure = std::make_exception_ptr( + std::runtime_error(io_error("Cannot write tty frame to", path_))); + } + impl_->data_ready_cv.notify_all(); + return; + } + if (written == 0) { + std::lock_guard lock(impl_->mutex); + if (!impl_->stop_requested) { + impl_->failure = std::make_exception_ptr( + std::runtime_error("tty write returned 0 bytes for '" + path_ + "'")); + } + impl_->data_ready_cv.notify_all(); + return; + } + + bytes += static_cast(written); + remaining -= static_cast(written); + } + + { + std::lock_guard lock(impl_->mutex); + impl_->stats.frames_written += static_cast(batch_count); + } + } +} + +#endif diff --git a/data_acq_and_processing/kamil_adc_collector/vendor/lcard/e502api.h b/data_acq_and_processing/kamil_adc_collector/vendor/lcard/e502api.h new file mode 100644 index 0000000..040d4ba --- /dev/null +++ b/data_acq_and_processing/kamil_adc_collector/vendor/lcard/e502api.h @@ -0,0 +1,1106 @@ +#ifndef E502API_H +#define E502API_H + +#include "x502api.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/***************************************************************************//** + @addtogroup const_list + @{ + *****************************************************************************/ +/** @brief Ñîáûòèÿ ïîèñêà ñåòåâûõ ñåðâèñîâ + + Êîäû ñîáûòèé, âîçíèêàþùèõ ïðè ïîèñêå ñåòåâûõ ñåðâèñîâ, âîçâðàùàåìûå + ôóíêöèåé E502_EthSvcBrowseGetEvent() */ +typedef enum { + E502_ETH_SVC_EVENT_NONE = 0, /**< Íè îäíîãî ñîáûòèÿ íå ïðîèçîøëî */ + E502_ETH_SVC_EVENT_ADD = 1, /**< Îáíàðóæåíî ïîÿâëåíèå íîâîãî ñåòåâîãî ñåðâèñà */ + E502_ETH_SVC_EVENT_REMOVE = 2, /**< Îáíàðóæåíî èñ÷åçíîâåíèå ðàíåå äîñòóïíîãî + ñåòåâîãî ñåðâèñà */ + E502_ETH_SVC_EVENT_CHANGED = 3 /**< Èçìåíåíèå ïàðàìåòðîâ ðàíåå îáíàðóæåííîãî + ñåòåâîãî ñåðâèñà */ +} t_e502_eth_svc_event; + +/** @} */ + +/***************************************************************************//** + @addtogroup type_list + @{ +*******************************************************************************/ + +/***************************************************************************//** + @brief Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + + Íåïðîçðà÷íûé óêàçàòåëü íà ñòðóêòóðó, ñîäåðæàùóþ ïàðàìåòðû êîíôèãóðàöèè + ñåòåâîãî èíòåðôåéñà ìîäóëÿ E-502. + Ïîëüçîâàòåëüñêîé ïðîãðàììå íå äîñòóïíû ïîëÿ ñòðóêòóðû íàïðÿìóþ, à òîëüêî + ÷åðåç ôóíêöèè áèáëèîòåêè. + Îïèñàòåëü êîíôèãóðàöèè ñîçäàåòñÿ ñ ïîìîùüþ E502_EthConfigCreate() è â êîíöå ðàáîòû + îñâîáîæäàåòñÿ ñ ïîìîùüþ E502_EthConfigFree(). + Êàê ïðàâèëî âñå íàñòðîéêè íå äîëæíû çàïîëíÿòüñÿ ïîëüçîâàòåëåì âðó÷íóþ, + îáû÷íî ñïåðâà îíè ñ÷èòûâàþòñÿ èç óñòðîéñòâà ñ ïîìîùüþ E502_EthConfigRead(), + ïîñëå ÷åãî ÷àñòü íàñòðîåê ìîæíî èçìåíèòü è ñîõðàíèòü â ìîäóëü ÷åðåç + E502_EthConfigWrite() + *****************************************************************************/ +typedef struct st_e502_eth_config_state* t_e502_eth_config_hnd; + +/**************************************************************************//** + @brief Îïèñàòåëü êîíòåêñòà ïîèñêà óñòðîéñòâ â ñåòè + + Óêàçàòåëü íà íåïðîçðà÷íóþ ñòðóêòóðó ñ èíôîðìàöèåé î ñîñòîÿíèè òåêóùåãî + ñåàíñà ïîèñêà óñòðîéñòâ â ñåòè. Ñîçäàåòñÿ ïðè íà÷àëå ïîèñêà âûçîâîì + E502_EthSvcBrowseStart() è óíè÷òîæàåòñÿ ñ ïîìîùüþ E502_EthSvcBrowseStop() + *****************************************************************************/ +typedef struct st_e502_eth_svc_browse_context *t_e502_eth_svc_browse_hnd; +/**************************************************************************//** + @brief Îïèñàòåëü ñåòåâîãî ñåðâèñà + + Óêàçàòåëü íà íåïðîçðà÷íóþ ñòðóêòóðó ñ èíôîðìàöèåé î ñåðâèñå â ñåòè, + ñîîòâåòñòâóþùåì îäíîìó ìîäóëþ E-502. Èñïîëüçóåòñÿ ïðè àâòîìàòè÷åñêîì îáíàðóæåíèè + óñòðîéñòâ â ëîêàëüíîé ñåòè. Ñîçäàåòñÿ ïðè âûçîâå E502_EthSvcBrowseGetEvent() + è óíè÷òîæàåòñÿ ñ ïîìîùüþ E502_EthSvcRecordFree() + *****************************************************************************/ +typedef struct st_e502_eth_svc_record *t_e502_eth_svc_record_hnd; + +/** @} */ + +/***************************************************************************//** + @addtogroup func_open + @{ +*******************************************************************************/ + +/***************************************************************************//** + @brief Ïîëó÷åíèå ñïèñêà ñåðèéíûõ íîìåðîâ ìîäóëåé E-502, ïîäêëþ÷åííûõ ïî USB + + Ôóíêöèÿ âîçâðàùàåò ñïèñîê íîìåðîâ âñåõ íàéäåííûõ ìîäóëåé E-502, íåçàâèñèìî îò + òîãî, îòêðûòû îíè ñåé÷àñ èëè íåò. + + Ôóíêöèÿ íà äàííûé ìîìåíò íå ïîääåðæèâàåò ôëàã #X502_GETDEVS_FLAGS_ONLY_NOT_OPENED. + + @param[in] serials Ìàññèâ ðàçìåðîì size*#X502_SERIAL_SIZE áàéò, â êîòîðûé + áóäóò ñîõðàíåíû ñåðèéíûå íîìåðà íàéäåííûõ ìîäóëåé. + Ìîæåò áûòü NULL, åñëè size=0, à devcnt!=NULL, â ñëó÷àå, + åñëè íóæíî òîëüêî ïîëó÷èòü êîëè÷åñòâî ìîäóëåé â ñèñòåìå. + @param[in] size Îïðåäåëÿåò, ñêîëüêî ìàêñèìàëüíî ñåðèéíûõ íîìåðîâ ìîæåò + áûòü ñîõðàíåíî â ìàññèâ serial. Áóäóò ñîõðàíåíû òîëüêî + ïåðâûå size ñåðèéíûõ íîìåðîâ. + Ìîæåò áûòü 0, åñëè serials=NULL + @param[in] flags Ôëàãè èç #t_x502_getdevs_flags, îïðåäåëÿþùèå ïîâåäåíèå + ôóíêöèè. + @param[out] devcnt Åñëè devcnt!=NULL, òî â äàííóþ ïåðåìåííóþ ñîõðàíÿåòñÿ + îáùåå ÷èñëî íàéäåííûõ ìîäóëåé E502 + (ìîæåò áûòü áîëüøå size). + @return Åñëè <0 - êîä îøèáêè, èíà÷å êîëè÷åñòâî ñîõðàíåííûõ + ñåðèéíûõ íîìåðîâ â ìàññèâå serials (âñåãäà <= size) +*******************************************************************************/ +X502_EXPORT(int32_t) E502_UsbGetSerialList(char serials[][X502_SERIAL_SIZE], uint32_t size, + uint32_t flags, uint32_t *devcnt); + +/***************************************************************************//** + @brief Ðàáîòàåò àíàëîãè÷íî E502_UsbGetSerialList, òîëüêî äëÿ ìîäóëåé E16 + ******************************************************************************/ +X502_EXPORT(int32_t) E16_UsbGetSerialList(char serials[][X502_SERIAL_SIZE], uint32_t size, + uint32_t flags, uint32_t *devcnt); + +/***************************************************************************//** + @brief Îòêðûòèå ìîäóëÿ E-502, ïîäêëþ÷åííîãî ïî USB, ïî åãî ñåðèéíîìó íîìåðó + + Ôóíêöèÿ óñòàíàâëèâàåò ñâÿçü ñ ìîäóëåì E-502, ïîäêëþ÷åííûì ïî èíòåðôåéñó USB, + ïî åãî ñåðèéíîìó íîìåðó. + + Ïîñëå óñïåøíîãî âûïîëíåíèÿ ýòîé ôóíêöèè, ïîëüçîâàòåëü ïîëó÷àåò ýêñêëþçèâíûé + äîñòóï ê ìîäóëþ ÷åðåç îïèñàòåëü ìîäóëÿ. Äî çàêðûòèÿ ñâÿçè ñ ïîìîùüþ + X502_Close() íèêòî äðóãîé óñòàíîâèòü ñâÿçü ñ ìîäóëåì íå ñìîæåò + (áóäåò âîçâðàùåíà îøèáêà #X502_ERR_DEVICE_ACCESS_DENIED). + + Åñëè â êà÷åñòâå ñåðèéíîãî íîìåðà ïåðåäàí NULL èëè ïóñòàÿ ñòðîêà, òî áóäåò + óñòàíîâëåíà ñâÿçü ñ ïåðâûì íàéäåííûì ìîäóëåì, ñ êîòîðûì ïîëó÷èòñÿ óñïåøíî + åå óñòàíîâèòü. + Åñëè â ñèñòåìå íåò íè îäíîãî ìîäóëÿ, òî áóäåò âîçâðàùåíà îøèáêà + #X502_ERR_DEVICE_NOT_FOUND. Åñëè â ñèñòåìå ïðèñóòñòâóþò ìîäóëè E-502, íî + ñîåäèíåíèå íè ñ îäíèì èç íèõ óñòàíîâèòü íå óäàëîñü, òî áóäåò âîçâðàùåíà + îøèáêà, ïîëó÷åííàÿ ïðè ïîïûòêå óñòàíîâèòü ñîåäèíåíèå ñ ïîñëåäíèì + íàéäåííûì ìîäóëåì. + + Ïîñëå çàâåðøåíèÿ ðàáîòû ñ óñòðîéñòâîì ñîåäèíåíèå äîëæíî áûòü çàêðûòî ñ + ïîìîùüþ X502_Close(). + + @param[in] hnd Îïèñàòåëü óñòðîéñòâà. + @param[in] serial Óêàçàòåëü íà ñòðîêó ñ ñåðèéíûì íîìåðîì îòêðûâàåìîãî + ìîäóëÿ èëè NULL. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) E502_OpenUsb(t_x502_hnd hnd, const char *serial); + +/***************************************************************************//** + @brief Ðàáîòàåò àíàëîãè÷íî E502_OpenUsb, òîëüêî äëÿ ìîäóëåé E16 + ******************************************************************************/ +X502_EXPORT(int32_t) E16_OpenUsb(t_x502_hnd hnd, const char *serial); + +/***************************************************************************//** + @brief Îòêðûòèå ìîäóëÿ E-502 ïî IP-àäðåñó + + Ôóíêöèÿ óñòàíàâëèâàåò ñâÿçü ñ ìîäóëåì E-502, ïîäêëþ÷åííûì ïî èíòåðôåéñó Ethernet, + äëÿ êîòîðîãî óñòàíîâëåí óêàçàííûé àäðåñ IPv4. + + Ïîñëå çàâåðøåíèÿ ðàáîòû ñ óñòðîéñòâîì ñîåäèíåíèå äîëæíî áûòü çàêðûòî ñ + ïîìîùüþ X502_Close(). + + @param[in] hnd Îïèñàòåëü óñòðîéñòâà. + @param[in] ip_addr IPv4 àäðåñ ìîäóëÿ â âèäå 32-áèòíîãî ñëîâà. + Äëÿ àäðåñà "a.b.c.d" ip_addr = (a<<24)|(b<<16)|(c<<8)|d. + @param[in] flags Ôëàãè, óïðàâëÿþùèå ðàáîòîé ôóíêöèè. Ðåçåðâ, äîëæíû áûòü âñåãäà 0. + @param[in] tout Âðåìÿ íà óñòàíîâëåíèÿ ïîäêëþ÷åíèÿ â ìñ. Åñëè ïîäêëþ÷åíèå + íå óäàñòñÿ çàâåðøèòü çà çàäàííîå âðåìÿ, òî ôóíêöèÿ âåðíåò îøèáêó. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) E502_OpenByIpAddr(t_x502_hnd hnd, uint32_t ip_addr, + uint32_t flags, uint32_t tout); + +/***************************************************************************//** + @brief Îòêðûòèå ìîäóëÿ E-16 ïî IP-àäðåñó + + Ôóíêöèÿ óñòàíàâëèâàåò ñâÿçü ñ ìîäóëåì E-16, ïîäêëþ÷åííûì ïî èíòåðôåéñó Ethernet, + äëÿ êîòîðîãî óñòàíîâëåí óêàçàííûé àäðåñ IPv4. + + Ïîñëå çàâåðøåíèÿ ðàáîòû ñ óñòðîéñòâîì ñîåäèíåíèå äîëæíî áûòü çàêðûòî ñ + ïîìîùüþ X502_Close(). + + @param[in] hnd Îïèñàòåëü óñòðîéñòâà. + @param[in] ip_addr IPv4 àäðåñ ìîäóëÿ â âèäå 32-áèòíîãî ñëîâà. + Äëÿ àäðåñà "a.b.c.d" ip_addr = (a<<24)|(b<<16)|(c<<8)|d. + @param[in] flags Ôëàãè, óïðàâëÿþùèå ðàáîòîé ôóíêöèè. Ðåçåðâ, äîëæíû áûòü âñåãäà 0. + @param[in] tout Âðåìÿ íà óñòàíîâëåíèÿ ïîäêëþ÷åíèÿ â ìñ. Åñëè ïîäêëþ÷åíèå + íå óäàñòñÿ çàâåðøèòü çà çàäàííîå âðåìÿ, òî ôóíêöèÿ âåðíåò îøèáêó. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) E16_OpenByIpAddr(t_x502_hnd hnd, uint32_t ip_addr, + uint32_t flags, uint32_t tout); + + +/** @} */ + +/***************************************************************************//** + @addtogroup func_devrec + @{ +*******************************************************************************/ + +/***************************************************************************//** + @brief Ïîëó÷èòü ñïèñîê çàïèñåé, ñîîòâåòñòâóþùèõ ïîäêëþ÷åííûì ìîäóëÿì E502 + + Ôóíêöèÿ íàõîäèò âñå ïîäêëþ÷åííûå ïî èíòåðôåéñó USB ìîäóëè E-502 è èíèöèàëèçèðóåò + çàïèñè î êàæäîì íàéäåííîì óñòðîéñòâå è ñîõðàíÿåò èõ â ïåðåäàííûé ñïèñîê + (åñëè íå íóëåâîé). + Âîçâðàùåííûå â ñïèñêå çàïèñè äîëæíû áûòü î÷èùåíû ïîñëå èñïîëüçîâàíèÿ + ñ ïîìîùüþ X502_FreeDevRecordList() (òàêæå â ñëó÷àå ïîâòîðíîãî + âûçîâ E502_UsbGetDevRecordsList() ñ òåì æå ìàññèâîì çàïèñåé, çàïèñè, ïîëó÷åííûå + ïðè ïðåäûäóùåì âûçîâå, äîëæíû áûòü ñïåðâà î÷èùåíû). + + @param[out] list Ìàññèâ äëÿ ñîõðàíåíèÿ çàïèñåé î íàéäåííûõ óñòðîéñòâàõ. + Äîëæåí ñîäåðæàòü ìåñòî äëÿ ñîõðàíåíèÿ íå ìåíåå size çàïèñåé. + Ìîæåò áûòü NULL, åñëè size=0, à devcnt!=NULL, â ñëó÷àå, + åñëè íóæíî òîëüêî ïîëó÷èòü êîëè÷åñòâî ìîäóëåé â ñèñòåìå. + @param[in] size Îïðåäåëÿåò, ñêîëüêî ìàêñèìàëüíî çàïèñåé ìîæåò + áûòü ñîõðàíåíî â ìàññèâ list. Áóäóò ñîõðàíåíû òîëüêî + ïåðâûå size çàïèñåé, åñëè óñòðîéñòâ íàéäåíî áîëüøå. + @param[in] flags Ôëàãè èç #t_x502_getdevs_flags, îïðåäåëÿþùèå ïîâåäåíèå + ôóíêöèè. + @param[out] devcnt Åñëè íå íóëåâîé óêàçàòåëü, òî â äàííóþ ïåðåìåííóþ ñîõðàíÿåòñÿ + îáùåå ÷èñëî íàéäåííûõ ìîäóëåé E-502, ïîäêëþ÷åííûõ ïî + èíòåðôåéñó USB (ìîæåò áûòü áîëüøå size). + @return Åñëè <0 --- êîä îøèáêè, èíà÷å êîëè÷åñòâî ñîõðàíåííûõ + çàïèñåé î íàéäåííûõ óñòðîéñòâàõ (âñåãäà <= size). + Èìåííî íà ýòîò ðàçìåð íóæíî ñäåëàòü â äàëüíåéøåì + X502_FreeDevRecordList() äëÿ îñâîáîæäåíèÿ ïàìÿòè, + âûäåëåííîé ïîä èíôîðìàöèþ, íà êîòîðóþ ññûëàåòñÿ çàïèñü. + ******************************************************************************/ +X502_EXPORT(int32_t) E502_UsbGetDevRecordsList(t_x502_devrec* list, uint32_t size, + uint32_t flags, uint32_t* devcnt) ; + + +/***************************************************************************//** + @brief Àíàëîãè÷íî E502_UsbGetDevRecordsList, ïîëó÷èòü ñïèñîê çàïèñåé, ñîîòâåòñòâóþùèõ ïîäêëþ÷åííûì ìîäóëÿì E16 + ******************************************************************************/ +X502_EXPORT(int32_t) E16_UsbGetDevRecordsList(t_x502_devrec* list, uint32_t size, + uint32_t flags, uint32_t* devcnt) ; + +/***************************************************************************//** + @brief Àíàëîãè÷íî E502_UsbGetDevRecordsList, ïîëó÷èòü ñïèñîê çàïèñåé, ñîîòâåòñòâóþùèõ ïîäêëþ÷åííûì ìîäóëÿì E14-440 + ******************************************************************************/ +X502_EXPORT(int32_t) E440_UsbGetDevRecordsList(t_x502_devrec* list, uint32_t size, + uint32_t flags, uint32_t* devcnt) ; + + +/***************************************************************************//** + @brief Ïîëó÷èòü ñïèñîê çàïèñåé, ñîîòâåòñòâóþùèõ ïîäêëþ÷åííûì ìîäóëÿì E502 + + Äåëàåò òîæå ñàìîå ÷òî è E502_UsbGetDevRecordsList, îòëè÷èå â òîì ÷òî ìîæíî óêàçàòü + idVendor è idProduct USB óñòðîéñòâà. + + @param[in] idVendor idVendor + @param[in] idProduct idProduct + @return Åñëè <0 --- êîä îøèáêè, èíà÷å êîëè÷åñòâî ñîõðàíåííûõ + çàïèñåé î íàéäåííûõ óñòðîéñòâàõ (âñåãäà <= size). + Èìåííî íà ýòîò ðàçìåð íóæíî ñäåëàòü â äàëüíåéøåì + X502_FreeDevRecordList() äëÿ îñâîáîæäåíèÿ ïàìÿòè, + âûäåëåííîé ïîä èíôîðìàöèþ, íà êîòîðóþ ññûëàåòñÿ çàïèñü. + ******************************************************************************/ +X502_EXPORT(int32_t) E502_UsbGetDevRecordsList2(t_x502_devrec *list, uint32_t size, + uint32_t flags, uint32_t *devcnt, uint16_t idVendor, uint16_t idProduct); + +/***************************************************************************//** + @brief Ñîçäàíèå çàïèñè î óñòðîéñòâå ñ óêàçàííûì IP-àäðåñîì + + Äàííàÿ ôóíêöèÿ èíèöèàëèçèðóåò çàïèñü î óñòðîéñòâå, ïîäêëþ÷åííîì ïî èíòåðôåéñó + Ethernet, ñ óêàçàííûì IPv4 àäðåñîì. Äàííàÿ ôóíêöèÿ òîëüêî ñîçäàåò çàïèñü, íî + íå ïðîâåðÿåò íàëè÷èå ñîîòâåòñòâóþùåãî óñòðîéñòâà. Ïîäêëþ÷åíèå ê ìîäóëþ + âûïîëíÿåòñÿ àíàëîãè÷íî äðóãèì çàïèñÿì ÷åðåç X502_OpenByDevRecord(). + + @param[out] devrec Óêàçàòåëü íà çàïèñü óñòðîéñòâà, êîòîðàÿ äîëæíà áûòü + ñîçäàíà è çàïîëíåíà íóæíûìè ïàðàìåòðàìè. + @param[in] ip_addr IPv4 àäðåñ ìîäóëÿ â âèäå 32-áèòíîãî ñëîâà (àíàëîãè÷íî + ïàðàìåòðó ip_addr ôóíêöèè E502_OpenByIpAddr()). + @param[in] flags Ôëàãè. Ðåçåðâ, äîëæíû áûòü âñåãäà 0. + @param[in] tout Âðåìÿ äëÿ óñòàíîâëåíèÿ ïîäêëþ÷åíèÿ â ìñ. + Äàííîå âðåìÿ ñîõðàíÿåòñÿ â çàïèñè è èñïîëüçóåòñÿ ïðè + ïîñëåäóþùåì âûçîâå X502_OpenByDevRecord(). Åñëè ïîäêëþ÷åíèå + íå óäàñòñÿ çàâåðøèòü çà ýòî âðåìÿ, òî ôóíêöèÿ + X502_OpenByDevRecord() âåðíåò îøèáêó. + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_MakeDevRecordByIpAddr(t_x502_devrec *devrec, uint32_t ip_addr, + uint32_t flags, uint32_t tout); + +/***************************************************************************//** + @brief Ñîçäàíèå çàïèñè î óñòðîéñòâå ñ óêàçàííûì IP-àäðåñîì + + Ðàáîòàåò àíàëîãè÷íî ôóíêöèè E502_MakeDevRecordByIpAddr. + Äëÿ ïîääåðæêè E16 äîáàâëåí ïàðàìåòð *devname. + + @param[out] devrec Óêàçàòåëü íà çàïèñü óñòðîéñòâà, êîòîðàÿ äîëæíà áûòü + ñîçäàíà è çàïîëíåíà íóæíûìè ïàðàìåòðàìè. + @param[in] ip_addr IPv4 àäðåñ ìîäóëÿ â âèäå 32-áèòíîãî ñëîâà (àíàëîãè÷íî + ïàðàìåòðó ip_addr ôóíêöèè E502_OpenByIpAddr()). + @param[in] flags Ôëàãè. Ðåçåðâ, äîëæíû áûòü âñåãäà 0. + @param[in] tout Âðåìÿ äëÿ óñòàíîâëåíèÿ ïîäêëþ÷åíèÿ â ìñ. + Äàííîå âðåìÿ ñîõðàíÿåòñÿ â çàïèñè è èñïîëüçóåòñÿ ïðè + ïîñëåäóþùåì âûçîâå X502_OpenByDevRecord(). Åñëè ïîäêëþ÷åíèå + íå óäàñòñÿ çàâåðøèòü çà ýòî âðåìÿ, òî ôóíêöèÿ + X502_OpenByDevRecord() âåðíåò îøèáêó. + @param[in] devname Èìÿ óñòðîéñòâà "E16" èëè "E502". + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_MakeDevRecordByIpAddr2(t_x502_devrec *devrec, uint32_t ip_addr, + uint32_t flags, uint32_t tout, char const *devname); + + +/***************************************************************************//** + @brief Óñòàíîâêà TCP-ïîðòà óïðàâëÿþùåãî ñîåäèíåíèÿ äëÿ çàïèñè î óñòðîéñòâå + + Äàííàÿ ôóíêöèÿ ïîçâîëÿåò èçìåíèòü TCP-ïîðò óïðàâëÿþùåãî ñîåäèíåíèÿ ìîäóëÿ + E-502. Ýòî ìîæåò áûòü íåîáõîäèìî, åñëè ìîäóëü E-502 è õîñò, ñ êîòîðîãî + íåîáõîäèìî óñòàíîâèòü ñîåäèíåíèå, íàõîäÿòñÿ â ðàçíûõ ñåòÿõ è àäðåñ ìîäóëÿ + E-502 íå äîñòóïåí èç ñåòè õîñòà.  ýòîì ñëó÷àå òðåáóåòñÿ íàñòðîéêà ïðîáðîñà + ïîðòîâ íà ìàðøðóòèçàòîðå è ïðè íàëè÷èå áîëåå îäíîãî òàêîãî ìîäóëÿ E-502, ò.ê + âñå ñîåäèíåíèÿ èäóò ñ ìàðøðóòèçèòîðîì, òî ðàçëè÷èòü ýòè ìîäóëè ìîæíî òîëüêî + ïî TCP-ïîðòó, åñëè íàñòðîèòü ðàçíûå ïîðòû ïðè ïðîáðîñå. +  ýòîì ñëó÷àå ïîìèìî ïîðòà óïðàâëÿþùåãî ñîåäèíåíèÿ, íåîáõîäèìî èçìåíèòü è + ïîðò ñîåäèíåíèÿ äëÿ ïåðåäà÷è äàííûõ, âûçâàâ E502_EthDevRecordSetDataPort(). + + Äàííàÿ ôóíêöèÿ äîëæíà áûòü âûçâàíà äëÿ çàïèñè, ñîçäàííîé äî ýòîãî ñ ïîìîùüþ + E502_MakeDevRecordByIpAddr() è äî îòêðûòèÿ ñîåäèíåíèÿ ñ ïîìîùüþ + X502_OpenByDevRecord(). + + @param[in] devrec Óêàçàòåëü íà çàïèñü óñòðîéñòâà, â êîòîðîé íóæíî èçìåíèòü + óïðàâëÿþùèé TCP-ïîðò. + @param[in] cmd_port Íîâîå çíà÷åíèå TCP-ïîðòà äëÿ óïðàâëÿþùåãî ñîåäèíåíèÿ + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthDevRecordSetCmdPort(t_x502_devrec *devrec, uint16_t cmd_port); + +/***************************************************************************//** + @brief Óñòàíîâêà TCP-ïîðòà ñîåäèíåíèÿ ïåðåäà÷è äàííûõ äëÿ çàïèñè î óñòðîéñòâå + + Ôóíêöèÿ àíàëîãè÷íà E502_EthDevRecordSetCmdPort(), íî èçìåíÿåò TCP-ïîðò äëÿ + ñîåäèíåíèÿ, ïî êîòîðîìó èäåò îáìåí äàííûõ ïîòîêîâ ââîäà-âûâîäà. + + @param[in] devrec Óêàçàòåëü íà çàïèñü óñòðîéñòâà, â êîòîðîé íóæíî èçìåíèòü + óïðàâëÿþùèé TCP-ïîðò. + @param[in] data_port Íîâîå çíà÷åíèå TCP-ïîðòà äëÿ ñîåäèíåíèÿ ïåðåäà÷è äàííûõ + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthDevRecordSetDataPort(t_x502_devrec *devrec, uint16_t data_port); + + + +/***************************************************************************//** + @brief Ñîçäàíèå çàïèñè î óñòðîéñòâå ïî îïèñàòåëþ ñåòåâîãî ñåðâèñà + + Äàííàÿ ôóíêöèÿ èíèöèàëèçèðóåò çàïèñü î óñòðîéñòâå, ïîäêëþ÷åííîì ïî èíòåðôåéñó + Ethernet, ñîîòâåòñòâóþùåìó ñåòåâîìó ñåðâèñó, íà êîòîðûé óêàçûâàåò ïåðåäàííûé + îïèñàòåëü ñåòåâîãî ñåðâèñà. Ýòîò îïèñàòåëü ìîæåò áûòü ïîëó÷åí ñ ïîìîùüþ + ôóíêöèé ïîèñêà ñåòåâûõ ñåðâèñîâ, ñîîòâåòñòâóþùèõ ìîäóëÿì E-502, â ëîêàëüíîé ñåòè. + Äàííàÿ ôóíêöèÿ òîëüêî ñîçäàåò çàïèñü, íî íå ïðîâåðÿåò íàëè÷èå ñîîòâåòñòâóþùåãî + óñòðîéñòâà. Ïîäêëþ÷åíèå ê ìîäóëþ âûïîëíÿåòñÿ àíàëîãè÷íî äðóãèì + çàïèñÿì ÷åðåç X502_OpenByDevRecord(). + Âñÿ íåîáõîäèìàÿ èíôîðìàöèÿ èç îïèñàòåëÿ ñåòåâîãî ñåðâèñà ñîõðàíÿåòñÿ â çàïèñè + î óñòðîéñòâå, ò.å. ïîñëå âûçîâà äàííîé ôóíöèè ïðè æåëàíèè îïèñàòåëü + ñåòåâîãî ñåðâèñà ìîæíî ñðàçó îñâîáîæäàòü ñ ïîìîùüþ E502_EthSvcRecordFree(). + + @param[out] devrec Óêàçàòåëü íà çàïèñü óñòðîéñòâà, êîòîðàÿ äîëæíà áûòü + ñîçäàíà è çàïîëíåíà íóæíûìè ïàðàìåòðàìè. + @param[in] svc Îïèñàòåëü ñåòåâîãî ñåðâèñà, ïîëó÷åííûé ñ ïîìîùüþ + E502_EthSvcBrowseGetEvent(). + @param[in] flags Ôëàãè. Ðåçåðâ, äîëæíû áûòü âñåãäà 0. + @param[in] tout Âðåìÿ äëÿ óñòàíîâëåíèÿ ïîäêëþ÷åíèÿ â ìñ. + Äàííîå âðåìÿ ñîõðàíÿåòñÿ â çàïèñè è èñïîëüçóåòñÿ ïðè + ïîñëåäóþùåì âûçîâå X502_OpenByDevRecord(). Åñëè ïîäêëþ÷åíèå + íå óäàñòñÿ çàâåðøèòü çà ýòî âðåìÿ, òî ôóíêöèÿ + X502_OpenByDevRecord() âåðíåò îøèáêó. + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_MakeDevRecordByEthSvc(t_x502_devrec *devrec, t_e502_eth_svc_record_hnd svc, + uint32_t flags, uint32_t tout); + +/** @} */ + + +/***************************************************************************//** + @addtogroup func_eth_config Ôóíêöèè äëÿ íàñòðîéêè ñåòåâûõ ïàðàìåòðîâ ìîäóëÿ E502 + @{ +*******************************************************************************/ + + + + +/***************************************************************************//** + @brief Ïîëó÷åíèå òåêóùåãî IP-àäðåñà óñòðîéñòâà + + Ôóíêöèÿ âîçâðàùàåò IP-àäðåñ óñòðîéñòâà ïî êîòîðîìó áûëî óñòàíîâëåíî ñîåäèíåíèå. + Òî åñòü ñâÿçü ñ óñòðîéñòâîì äîëæíà áûòü óæå óñòàíîâëåíà è êðîìå òîãî, + óñòàíîâëåíà èìåííî ïî èíòåðôåéñó Ethernet. + @param[in] hnd Îïèñàòåëü óñòðîéñòâà + @param[out] ip_addr Òåêóùèé IPv4 àäðåñ ìîäóëÿ â âèäå 32-áèòíîãî ñëîâà (àíàëîãè÷íî + ïàðàìåòðó ip_addr ôóíêöèè E502_OpenByIpAddr()). + + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_GetIpAddr(t_x502_hnd hnd, uint32_t *ip_addr); + + + +/***************************************************************************//** + @brief Ñîçäàíèå îïèñàòåëÿ êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + + Ñîçäàíèå îïèñàòåëÿ êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. +  ñëó÷àå óñïåøíîãî âûäåëåíèÿ ïàìÿòè èíèöèàëèçèðóåò ïîëÿ îïèñàòåëÿ + çíà÷åíèÿìè ïî-óìîë÷àíèþ. + @return NULL â ñëó÷àå îøèáêè, èíà÷å - îïèñàòåëü ìîäóëÿ +*******************************************************************************/ +X502_EXPORT(t_e502_eth_config_hnd) E502_EthConfigCreate(void); + +/***************************************************************************//** + @brief Îñâîáîæäåíèå îïèñàòåëÿ êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + + Îñâîáîæäåíèå ïàìÿòè, âûäåëåííîé ïîä îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + ñ ïîìîùüþ E502_EthConfigCreate(). + Ïîñëå ýòîãî îïèñàòåëü óæå èñïîëüçîâàòü íåëüçÿ, íåçàâèñèìî îò âîçâðàùåííîãî + çíà÷åíèÿ! + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigFree(t_e502_eth_config_hnd cfg); + + + +/***************************************************************************//** + @brief ×òåíèå òåêóùåé ñåòåâîé êîíôèãóðàöèè èíòåðôåéñà + + Ôóíêöèÿ ÷èòàåò òåêóùèå ïàðàìåòðû èíòåðôåéñà è ñîõðàíÿåò èõ â ñòðóêòóðó, + íà êîòîðóþ óêàçûâàåò ñîçäàííûé ñ ïîìîùüþ E502_EthConfigCreate() îïèñàòåëü + êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + + Ñîåäèíåíèå ñ óñòðîéñòâîì ïðè ýòîì äîëæíî áûòü óñòàíîâëåíî, íî ìîæåò áûòü + óñòàíîâëåíî ïî ëþáîìó ïîääåðæèâàåìîìó èíòåðôåéñó. + + @param[in] hnd Îïèñàòåëü óñòðîéñòâà èç êîòîðîãî íóæíî ñ÷èòàòü êîíôèãóðàöèþ + @param[in,out] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigRead(t_x502_hnd hnd, t_e502_eth_config_hnd cfg); + + +/***************************************************************************//** + @brief Çàïèñü ñåòåâîé êîíôèãóðàöèè èíòåðôåéñà + + Ôóíêöèÿ ïåðåäàåò ìîäóëþ êîíôèãóðàöèþ ñåòåâîãî èíòåðôåéñà, êîòîðóþ ìîäóëü + äîëæåí ñîõðàíèòü â ñâîåé ýíåðãîíåçàâèñèìîé ïàìÿòè. + + Ïðè óñïåøíîì âûïîëíåíèè äàííîé ôóíêöèè ìîäóëü îòêëþ÷àåò Ethernet-èíòåðôåéñ, + íàñòðàèâàåò åãî íà íîâûå ïàðàìåòðû è ñíîâà åãî èíèöèàëèçèðóåò, ïîýòîìó + åñëè ñîåäèíåíèå ñ óñòðîéñòâîì óñòàíîâëåíî ïî ñåòè, òî äàëüíåéøàÿ ðàáîòà + ñ óñòðîéñòâîì áóäåò óæå íå âîçìîæíà --- íåîáõîäèìî çàêðûòü ñâÿçü ñ óñòðîéñòâîì + è óñòàíîâèòü åå çàíîâî. + + Äëÿ èçìåíåíèÿ êîíôèãóðàöèè íåîáõîäèìî ïåðåäàòü ïàðîëü äëÿ êîíôèãóðàöèè + (ïóñòàÿ ñòðîêà, åñëè ïàðîëü íå áûë óñòàíîâëåí). Ïðè ðàáîòå ïî USB èíòåðôåéñó + â êà÷åñòâå ïàðîëÿ ìîæíî ïåðåäàòü òåêóùèé ñåðèéíûé íîìåð óñòðîéñòâà + (äëÿ ñëó÷àÿ, åñëè çàáûò óñòàíîâëåííûé ïàðîëü). + + @param[in] hnd Îïèñàòåëü óñòðîéñòâà èç êîòîðîãî íóæíî ñ÷èòàòü êîíôèãóðàöèþ + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + @param[in] passwd Ñòðîêà ñ ïàðîëåì äëÿ èçìåíåíèÿ êîíôèãóðàöèè + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigWrite(t_x502_hnd hnd, t_e502_eth_config_hnd cfg, + const char *passwd); + + +/***************************************************************************//** + @brief Êîïèðîâàíèå ñîäåðæèìîãî ñåòåâîé êîíôèãóðàöèè èíòåðôåéñà + + Ôóíêöèÿ âûïîëíÿåò êîïèðîâàíèå âñåõ ïàðàìåòðîâ îäíîé ñîçäàííîé êîíôèãóðàöèè + â äðóãóþ êîíôèðóðàöèþ, ñîçäàâàÿ ïîëíóþ êîïèþ. + + @param[in] src_cfg Îïèñàòåëü èñõîäíîé ñåòåâîé êîíôèãóðàöèè èíòåðôåéñà, + ñîäåðæèìîå êîòîðîé íóæíî ñêîïèðîâàòü. + @param[out] dst_cfg Îïèñàòåëü ñåòåâîé êîíôèãóðàöèè èíòåðôåéñà, â êîòîðóþ + íóæíî ñêîïèðîâàòü ñîäåðæèìîå èñõîäíîé êîíôèãóðàöèè + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigCopy(t_e502_eth_config_hnd src_cfg, + t_e502_eth_config_hnd dst_cfg); + +/***************************************************************************//** + @brief Îïðåäåëåíèå, ðàçðåøåí ëè èíòåðôåéñ Ethernet + + Ôóíêöèÿ âîçâðàùàåò, ðàçðåøåí ëè èíòåðôåéñ Ethernet â óêàçàííîé êîíôèãóðàöèè. + Åñëè èíòåðôåéñ íå ðàçðåøåí, òî Ethernet êîíòðîëëåð ïîëíîñòüþ îòêëþ÷åí. + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + @param[out] en Åñëè èíòåðôåéñ ðàçðåøåí, òî â äàííîé ïåðåìåííîé âîçâðàùàåòñÿ 1, + èíà÷å --- 0 + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigGetEnabled(t_e502_eth_config_hnd cfg, uint32_t *en); + +/***************************************************************************//** + @brief Ðàçðåøåíèå èíòåðôåéñà Ethernet + + Ôóíêöèÿ óñòàíàâëèâàåò, ðàçðåøåíà ëè ðàáîòà ïî èíòåðôåéñó Ethernet. + Åñëè èíòåðôåéñ íå ðàçðåøåí, òî Ethernet êîíòðîëëåð ïîëíîñòüþ îòêëþ÷åí. + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + @param[in] en 0 îçíà÷àåò çàïðåò èíòåðôåéñà Ethernet, 1 --- ðàçðåøåíèå + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigSetEnabled(t_e502_eth_config_hnd cfg, uint32_t en); +/***************************************************************************//** + @brief Îïðåäåëåíèå, ðàçðåøåíî ëè àâòîìàòè÷åñêîå ïîëó÷åíèå ïàðàìåòðîâ IP + + Ôóíêöèÿ âîçâðàùàåò, ðàçðåøåíî ëè àâòîìàòè÷åñêîå ïîëó÷åíèå ïàðàìåòðîâ IP + (IP-àäðåñ, ìàñêà ïîäñåòè, àäðåñ øëþçà) ñ èñïîëüçîâàíèåì DHCP/linklocal èëè + èñïîëüçóþòñÿ ñòàòè÷åñêè çàäàííûå ïàðàìåòðû. + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + @param[out] en Åñëè ðàçðåøåíî àâòîìàòè÷åñêîå ïîëó÷åíèå ïàðàìåòðîâ, òî + âîçâðàùàåòñÿ 1, èíà÷å --- 0 + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigGetAutoIPEnabled(t_e502_eth_config_hnd cfg, uint32_t *en); +/***************************************************************************//** + @brief Ðàçðåøåíèå àâòîìàòè÷åñêîãî ïîëó÷åíèÿ ïàðàìåòðîâ IP + + Ôóíêöèÿ óñòàíàâëèâàåò, ðàçðåøåíî ëè àâòîìàòè÷åñêîå ïîëó÷åíèå ïàðàìåòðîâ IP + (IP-àäðåñ, ìàñêà ïîäñåòè, àäðåñ øëþçà) ñ èñïîëüçîâàíèåì DHCP/linklocal èëè + èñïîëüçóþòñÿ ñòàòè÷åñêè çàäàííûå ïàðàìåòðû. + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + @param[in] en Åñëè ðàçðåøåíî àâòîìàòè÷åñêîå ïîëó÷åíèå ïàðàìåòðîâ, òî + âîçâðàùàåòñÿ 1, èíà÷å --- 0 + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigSetAutoIPEnabled(t_e502_eth_config_hnd cfg, uint32_t en); +/***************************************************************************//** + @brief Ïîëó÷èòü ñîñòîÿíèå àâòîìàòè÷åñêîãî ïîëó÷åíèÿ ïàðàìåòðîâ IP + + Ôóíêöèÿ âîçâðàùàåò, ïîëó÷èë ëè ìîäóëü ïàðàìåòðû IP + (IP-àäðåñ, ìàñêà ïîäñåòè, àäðåñ øëþçà) ñ èñïîëüçîâàíèåì DHCP/linklocal + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + @param[out] state Ñîñòîÿíèå àâòîìàòè÷åñêîãî ïîëó÷åíèÿ ïàðàìåòðîâ IP + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigGetAutoIPState(t_e502_eth_config_hnd cfg, uint32_t *state); + +/***************************************************************************//** + @brief Îïðåäåëåíèå, ðàçðåøåí ëè ïîëüçîâàòåëüñêèé MAC-àäðåñ + + Ôóíêöèÿ âîçâðàùàåò, ðàçðåøåí ëè MAC-àäðåñ, çàäàííûé ïîëüçîâàòåëåì, èëè + èñïîëüçóåòñÿ çàâîäñêîé MAC-àäðåñ. + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[out] en Åñëè ðàçðåøåí ïîëüçîâàòåëüñêèé MAC-àäðåñ, òî + âîçâðàùàåòñÿ 1, èíà÷å (åñëè èñïîëüçóåòñÿ çàâîäñêîé) --- 0 + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigGetUserMACEnabled(t_e502_eth_config_hnd cfg, uint32_t *en); +/***************************************************************************//** + @brief Îïðåäåëåíèå, ðàçðåøåí ëè ïîëüçîâàòåëüñêèé MAC-àäðåñ + + Ôóíêöèÿ âîçâðàùàåò, ðàçðåøåí ëè MAC-àäðåñ, çàäàííûé ïîëüçîâàòåëåì, èëè + èñïîëüçóåòñÿ çàâîäñêîé MAC-àäðåñ. + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[in] en Åñëè ðàçðåøåí ïîëüçîâàòåëüñêèé MAC-àäðåñ, òî + âîçâðàùàåòñÿ 1, èíà÷å (åñëè èñïîëüçóåòñÿ çàâîäñêîé) --- 0 + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigSetUserMACEnabled(t_e502_eth_config_hnd cfg, uint32_t en); + +/***************************************************************************//** + @brief Ïîëó÷åíèå óñòàíîâëåííîãî ñòàòè÷åñêîãî IP-àäðåñà + + Ôóíêöèÿ âîçâðàùàåò çàäàííûé â êîíôèãóðàöèè ñòàòè÷åñêèé IP-àäðåñ, êîòîðûé + èñïîëüçóåòñÿ óñòðîéñòâîì, åñëè çàïðåùåíî àâòîìàòè÷åñêîå ïîëó÷åíèå IP-ïàðàìåòðîâ. + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[out] ip_addr Çàäàííûé IP-àäðåñ â âèäå 32-áèòíîãî ñëîâà (àíàëîãè÷íî + ïàðàìåòðó ip_addr ôóíêöèè E502_OpenByIpAddr()). + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigGetIPv4Addr(t_e502_eth_config_hnd cfg, uint32_t *ip_addr); +/***************************************************************************//** + @brief Óñòàíîâêà ñòàòè÷åñêîãî IP-àäðåñà + + Ôóíêöèÿ óñòàíàâëèâàåò â êîíôèãóðàöèè çàäàííûé ñòàòè÷åñêèé IP-àäðåñ, êîòîðûé + áóäåò èñïîëüçîâàòüñÿ óñòðîéñòâîì, åñëè çàïðåùåíî àâòîìàòè÷åñêîå ïîëó÷åíèå IP-ïàðàìåòðîâ. + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[in] ip_addr Óñòàíàâëèâàåìûé IP-àäðåñ â âèäå 32-áèòíîãî ñëîâà (àíàëîãè÷íî + ïàðàìåòðó ip_addr ôóíêöèè E502_OpenByIpAddr()). + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigSetIPv4Addr(t_e502_eth_config_hnd cfg, uint32_t ip_addr); + +/***************************************************************************//** + @brief Ïîëó÷åíèå óñòàíîâëåííîé ñòàòè÷åñêîé ìàñêè ïîäñåòè + + Ôóíêöèÿ âîçâðàùàåò çàäàííîå â êîíôèãóðàöèè çíà÷åíèå ìàñêè ïîäñåòè, êîòîðàÿ + èñïîëüçóåòñÿ óñòðîéñòâîì, åñëè çàïðåùåíî àâòîìàòè÷åñêîå ïîëó÷åíèå IP-ïàðàìåòðîâ. + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[out] mask Ìàñêà ïîäñåòè â âèäå 32-áèòíîãî ñëîâà (àíàëîãè÷íî + ïàðàìåòðó ip_addr ôóíêöèè E502_OpenByIpAddr()). + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigGetIPv4Mask(t_e502_eth_config_hnd cfg, uint32_t *mask); +/***************************************************************************//** + @brief Óñòàíîâêà ñòàòè÷åñêîé ìàñêè ïîäñåòè + + Ôóíêöèÿ óñòàíàâëèâàåò â êîíôèãóðàöèè çíà÷åíèå ìàñêè ïîäñåòè, êîòîðàÿ áóäåò + èñïîëüçîâàòüñÿ óñòðîéñòâîì, åñëè çàïðåùåíî àâòîìàòè÷åñêîå ïîëó÷åíèå IP-ïàðàìåòðîâ. + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[in] mask Óñòàíàâëèâàåìîå çíà÷åíèå ìàñêè ïîäñåòè â âèäå 32-áèòíîãî ñëîâà + (àíàëîãè÷íî ïàðàìåòðó ip_addr ôóíêöèè E502_OpenByIpAddr()). + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigSetIPv4Mask(t_e502_eth_config_hnd cfg, uint32_t mask); + +/***************************************************************************//** + @brief Ïîëó÷åíèå óñòàíîâëåííîãî ñòàòè÷åñêîãî àäðåñà øëþçà + + Ôóíêöèÿ âîçâðàùàåò çàäàííîå â êîíôèãóðàöèè çíà÷åíèå àäðåñà øëþçà, êîòîðûé + èñïîëüçóåòñÿ óñòðîéñòâîì, åñëè çàïðåùåíî àâòîìàòè÷åñêîå ïîëó÷åíèå IP-ïàðàìåòðîâ. + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[out] gate Àäðåñ øëþçà â âèäå 32-áèòíîãî ñëîâà (àíàëîãè÷íî + ïàðàìåòðó ip_addr ôóíêöèè E502_OpenByIpAddr()). + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigGetIPv4Gate(t_e502_eth_config_hnd cfg, uint32_t *gate); +/***************************************************************************//** + @brief Óñòàíîâêà ñòàòè÷åñêîãî àäðåñà øëþçà + + Ôóíêöèÿ óñòàíàâëèâàåò â êîíôèãóðàöèè çíà÷åíèå àäðåñà øëþçà, êîòîðûé + áóäåò èñïîëüçîâàòüñÿ óñòðîéñòâîì, åñëè çàïðåùåíî àâòîìàòè÷åñêîå ïîëó÷åíèå IP-ïàðàìåòðîâ. + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[in] gate Óñòàíàâëèâàåìîå çíà÷åíèå àäðåñà øëþçà â âèäå 32-áèòíîãî ñëîâà + (àíàëîãè÷íî ïàðàìåòðó ip_addr ôóíêöèè E502_OpenByIpAddr()). + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigSetIPv4Gate(t_e502_eth_config_hnd cfg, uint32_t gate); + +/***************************************************************************//** + @brief Ïîëó÷åíèå óñòàíîâëåííîãî ïîëüçîâàòåëüñêîãî MAC-àäðåñà + + Ôóíêöèÿ âîçâðàùàåò çàäàííîå â êîíôèãóðàöèè çíà÷åíèå ïîëüçîâàòåëüñêîãî MAC-àäðåñà, + êîòîðûé èñïîëüçóåòñÿ óñòðîéñòâîì ïðè ÿâíîì åãî ðàçðåøåíèè (ñì. + E502_EthConfigSetUserMACEnabled()). + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[out] mac Ïîëüçîâàòåëüñêèé MAC-àäðåñ óñòðîéñòâà â âèäå ìàññèâà èç + #X502_MAC_ADDR_SIZE áàéò â ïîðÿäêå çàïèñè àäðåñà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigGetUserMac(t_e502_eth_config_hnd cfg, uint8_t *mac); +/***************************************************************************//** + @brief Óñòàíîâêà ïîëüçîâàòåëüñêîãî MAC-àäðåñà + + Ôóíêöèÿ óñòàíàâëèâàåò â êîíôèãóðàöèè çíà÷åíèå ïîëüçîâàòåëüñêîãî MAC-àäðåñà, + êîòîðûé áóäåò èñïîëüçîâàòüñÿ óñòðîéñòâîì ïðè ÿâíîì åãî ðàçðåøåíèè (ñì. + E502_EthConfigSetUserMACEnabled()). + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[in] mac Óñòàíàâëèâàåìîå çíà÷åíèå ïîëüçîâàòåëüñêîãî MAC-àäðåñ óñòðîéñòâà + â âèäå ìàññèâà èç #X502_MAC_ADDR_SIZE áàéò â ïîðÿäêå çàïèñè àäðåñà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigSetUserMac(t_e502_eth_config_hnd cfg, const uint8_t *mac); +/***************************************************************************//** + @brief Ïîëó÷åíèå çàâîäñêîãî MAC-àäðåñà óñòðîéñòâà + + Ôóíêöèÿ âîçâðàùàåò çíà÷åíèå çàâîäñêîãî MAC-àäðåñà óñòðîéñòâà, êîòîðîìó + ñîîòâåòñòâóåò ïåðåäàííàÿ ïåðâûì ïàðàìåòðîì êîíôèãóðàöèÿ. + Çàâîäñêîé MAC-àäðåñ, èñïîëüçóåìûé óñòðîéñòâîì ïî-óìîë÷àíèþ, çàïèñûâàåòñÿ + ïðîèçâîäèòåëåì (â "Ë Êàðä") ïðè ïðîèçâîäñòâå óñòðîéñòâà âìåñòå ñ åãî ñåðèéíûì + íîìåðîì è íå ìîæåò áûòü èçìåíåí ïîëüçîâàòåëåì. Åñëè ïîëüçîâàòåëþ íóæíî + èçìåíèòü MAC-àäðåñ óñòðîéñòâà, òî îí äîëæåí çàäàòü ïîëüçîâàòåëüñêèé + MAC-àäðåñ ñ ïîìîùüþ E502_EthConfigGetUserMac() è ðàçðåøèòü åãî èñïîëüçîâàíèå + ÷åðåç E502_EthConfigSetUserMACEnabled(). Ïðè ýòîì âñåãäà åñòü âîçìîæíîñòü + ñíîâà âåðíóòüñÿ ê èñïîëüçîâàíèþ îðèãèíàëüíîãî çàâîäñêîãî MAC-àäðåñà. + + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[out] mac Çàâîäñêîé MAC-àäðåñ óñòðîéñòâà â âèäå ìàññèâà èç + #X502_MAC_ADDR_SIZE áàéò â ïîðÿäêå çàïèñè àäðåñà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigGetFactoryMac(t_e502_eth_config_hnd cfg, uint8_t *mac); + + +/***************************************************************************//** + @brief Ïîëó÷åíèå óñòàíîâëåííîãî èìåíè ýêçåìïëÿðà óñòðîéñòâà + + Ôóíêöèÿ âîçâðàùàåò çàäàííîå ïîëüçîâàòåëåì èìÿ ýêçåìïëÿðà óñòðîéñòâà. Äàííîå + èìÿ ìîæåò èñïîëüçîâàòüñÿ äëÿ îáíàðóæåíèÿ óñòðîéñòâà â ñåòè. Åñëè íå çàäàíî, + òî èñïîëüçóåòñÿ èìÿ, îáðàçîâàííîå íàçâàíèåì óñòðîéñòâà è åãî ñåðèéíûì íîìåðîì. + Äàííîå èìÿ äîëæíî áûòü óíèêàëüíî â ïðåäåëàõ ñåòè. + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[out] name Îêàí÷èâàþùàÿñÿ íóëåì ñòðîêà ñ çàäàííûì èìåíåì ýêçåìïëÿðà óñòðîéñòâà â ôîðìàòå + UTF-8. Ìàññèâ äîëæåí áûòü ðàññ÷èòàí íà #X502_INSTANCE_NAME_SIZE + áàéò äàííûõ. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigGetInstanceName(t_e502_eth_config_hnd cfg, char *name); +/***************************************************************************//** + @brief Óñòàíîâêà èìåíè ýêçåìïëÿðà óñòðîéñòâà + + Ôóíêöèÿ óñòàíàâëèâàåò èìÿ ýêçåìïëÿðà óñòðîéñòâà, êîòîðîå ìîæåò èñïîëüçîâàòüñÿ + äëÿ îáíàðóæåíèÿ óñòðîéñòâà ëîêàëüíîé â ñåòè. + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[in] name Îêàí÷èâàþùàÿñÿ íóëåì ñòðîêà ñ çàäàííûì èìåíåì ýêçåìïëÿðà óñòðîéñòâà â ôîðìàòå + UTF-8. Ìàêñèìàëüíûé ðàçìåð ìàññèâà (âêëþ÷àÿ çàâåðøàþùèé íîëü) + ñîñòàâëÿåò #X502_INSTANCE_NAME_SIZE áàéò äàííûõ. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigSetInstanceName(t_e502_eth_config_hnd cfg, const char *name); + + +/***************************************************************************//** + @brief Óñòàíîâêà íîâîãî ïàðîëÿ äëÿ ñìåíû êîíôèãóðàöèè + + Ôóíêöèÿ óñòàíàâëèâàåò íîâîå çíà÷åíèå ïàðîëÿ, êîòîðîå äîëæíî áóäåò èñïîëüçîâàòüñÿ + äëÿ ñìåíû êîíôèãóðàöèè ÷åðåç E502_EthConfigWrite(). + + Ïðè ýòîì çíà÷åíèå ïðè ñîõðàíåíèè êîíôèãóðàöèè ñ óñòàíîâëåííûì íîâûì ïàðîëåì + íåîáõîäèìî äëÿ óñïåøíîé ñìåíû êîíôèãóðàöèè â E502_EthConfigWrite() ïåðåäàòü + çíà÷åíèå ïàðîëÿ, êîòîðîå áûëî óñòàíîâëåíî äî ýòîãî. Åñëè ôóíêöèÿ çàâåðøèòñÿ + óñïåøíî, òî äëÿ ïîñëåäóþùåãî èçìåíåíèÿ êîíôèãóðàöèè â E502_EthConfigWrite() + íóæíî áóäåò ïåðåäàâàòü óæå íîâîå óñòàíîâëåííîå çíà÷åíèå ïàðîëÿ. + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + @param[in] new_passwd Îêàí÷èâàþùàÿñÿ íóëåì ñòðîêà, ñîäåðæàùàÿ íîâîå çíà÷åíèå + ïàðîëÿ äëÿ ñìåíû êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà. + Ìàêñèìàëüíûé ðàçìåð ìàññèâà (âêëþ÷àÿ çàâåðøàþùèé íîëü) + ñîñòàâëÿåò #X502_PASSWORD_SIZE áàéò äàííûõ. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigSetNewPassword(t_e502_eth_config_hnd cfg, const char *new_passwd); + + +/** @} */ + + +/***************************************************************************//** + @addtogroup func_misc + @{ +*******************************************************************************/ + + +/***************************************************************************//** + @brief Ïåðåâîä ìîäóëÿ E-502 â ðåæèì çàãðóç÷èêà + + Ôóíêöèÿ ïåðåâîäèò óñòðîéñòâî â ðåæèì çàãðóç÷èêà äëÿ âîçìîæíîñòè îáíîâëåíèÿ + ïðîøèâêè êîíòðîëëåðà Cortex-M4 ìîäóëÿ E-502 ñ ïîìîùüþ óòèëèòû lboot. + +  çàâèñèìîñòè îò èñïîëüçóåìîãî òåêóùåãî èíòåðôåéñà äëÿ ñîåäèíåíèÿ ñ ìîäóëåì, + ìîäóëü ïåðåâîäèòñÿ â ðåæèì çàãðóçêè ïðîøèâêè ïî èíòåðôåéñó USB (åñëè ñîåäèíåíèå + áûëî ïî USB) èëè ïî TFTP (åñëè ñîåäèíåíèå áûëî ïî èíòåðôåéñó Ethernet). + + Ïðè óñïåøíîì âûçîâå äàííîé ôóíêöèè äàëüíåéøàÿ ðàáîòà ñ òåêóùåì ñîåäèíåíèåì íåâîçìîæíà, + ò.å. åäèíñòâåííûì äîïóñòèìûì ñëåäóþùèì âûçîâîì ÿâëÿåòñÿ X502_Close(). + + Ïðè ïåðåõîäå â çàãðóç÷èê íàõîäèòñÿ â ðåæèìå çàãðóç÷èêà ïîðÿäêà 30ñ ïîñëå ÷åãî, + åñëè íå ïîñòóïàëî çàïðîñîâ íà ïåðåïðîøèâêó çàãðóç÷èê âîçâðàùàåò óïðàâëåíèå øòàòíîé ïðîøèâêå. + Ïîêà ìîäóëü íàõîäèòñÿ â ðåæèìå çàãðóç÷èêà ñ íèì íåâîçìîæíî óñòàíîâèòü ñîåäèíåíèå + ñ ïîìîùüþ ôóíêöèé äàííîé áèáëèîòåêè. + + + @param[in] hnd Îïèñàòåëü óñòðîéñòâà. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) E502_SwitchToBootloader(t_x502_hnd hnd); + +/***************************************************************************//** + @brief Ïåðåçàãðóçêà ïðîøèâêè ÏËÈÑ + + Ïî äàííîé êîìàíäå êîíòðîëëåð Cortex-M4 ìîäóëÿ E-502 âûïîëíÿåò ñáðîñ ÏËÈÑ + è çàãðóçêó ïðîøèâêè ÏËÈÑ èç âíóòðåííåé Flash-ïàìÿòè. + + Ýòî ñåðâèñíàÿ ôóíêöèÿ, êîòîðàÿ èñïîëüçóåòñÿ ãëàâíûì îáðàçîì äëÿ îáíîâëåíèÿ + ïðîøèâêè ÏËÈÑ. + + @param[in] hnd Îïèñàòåëü óñòðîéñòâà. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) E502_ReloadFPGA(t_x502_hnd hnd); + + + +/***************************************************************************//** + @brief Ïåðåäà÷à óïðàâëÿþùåé êîìàíäû êîíòðîëëåðó Cortex-M4. + + Ôóíêöèÿ ïðåäíàçíà÷åíà äëÿ ïåðåäà÷è ïîëüçîâàòåëüñêèõ óïðàâëÿþùèõ êîìàíä + êîíòðîëëåðó äëÿ ñëó÷àÿ ìîäèôèöèðîâàííîé ïðîøèâêè Cortex-M4. + + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] cmd_code Êîä êîìàíäû - îïðåäåëÿåò, ÷òî çà êîìàíäà âûïîëíÿåòñÿ. + @param[in] par Ïàðàìåòð, ïåðåäàâàåìûé ñ êîìàíäîé (çíà÷åíèå çàâèñèò + îò êîäà êîìàíäû). + @param[in] snd_data Îïöèîíàëüíûå äàííûå, ïåðåäàâàåìûå âìåñòå ñ êîìàíäîé. + Åñëè äàííûå íå ïåðåäàþòñÿ, òî äîëæåí ïåðåäàâàòüñÿ + íóëåâîé óêàçàòåëü è snd_size = 0. + @param[in] snd_size Êîëè÷åñòâî áàéò, ïåðåäàâàåìûõ â snd_data + @param[out] rcv_data Ìàññèâ, â êîòîðûé áóäóò ïåðåäàíû äàííûå, âîçâðàùåííûå + ïðîöåññîðîì ïî çàâåðøåíèþ êîìàíäû. Åñëè äàííûå íå + äîëæíû âîçâðàùàòüñÿ, òî äîëæåí ïåðåäàâàòüñÿ íóëåâîé + óêàçàòåëü, à rcv_size = 0. + @param[in] rcv_size Êîëè÷åñòâî áàéò, êîòîðîå îæèäàåòñÿ, ÷òî + âåðíåò êîíòðîëëåð ïî âûïîëíåíèþ êîìàíäû. Ìàññèâ + rcv_data äîëæåí áûòü ðàññ÷èòàí íà äàííîå êîëè÷åñòâî + ñëîâ. + @param[in] tout Òàéìàóò â òå÷åíèè êîòîðîãî áóäåò îæèäàòüñÿ, êîãäà + êîíòðîëëåð çàâåðøèò âûïîëíåíèå êîìàíäû. Ôóíêöèÿ + âîçâðàòèò óïðàâëåíèå ëèáî ïî çàâåðøåíèþ êîìàíäû, + ëèáî ïî òàéìàóòó. + @param[out] recvd_size Åñëè íå ÿâëÿåòñÿ íóëåâûì óêàçàòåëåì, òî â ýòó + ïåðåìåííóþ áóäåò ñîõðàíåíî êîëè÷åñòâî áàéò, + êîòîðîå ðåàëüíî âåðíóë êîíòðîëëåð ïîñëå âûïîëíåíèÿ + êîìàíäû (êîíòðîëëåð èìååò ïðàâî âåðíóòü ìåíüøå äàííûõ, + ÷åì çàïðàøèâàëîñü â rcv_size). Åñëè óêàçàòåëü íóëåâîé, + òî âîçâðàùàåíèå ìåíüøåãî êîëè÷åñòâà äàííûõ ñ÷èòàåòñÿ + îøèáêîé. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) E502_CortexExecCmd(t_x502_hnd hnd, uint32_t cmd_code, uint32_t par, + const uint8_t* snd_data, uint32_t snd_size, + uint8_t* rcv_data, uint32_t rcv_size, + uint32_t tout, uint32_t* recvd_size); + + +/** @} */ + + + +/***************************************************************************//** + @addtogroup func_eth_svc_browse Ôóíêöèè äëÿ ïîèñêà ìîäóëåé â ëîêàëüíîé ñåòè + @{ +*******************************************************************************/ + +/***************************************************************************//** + @brief Íà÷àëî ñåàíñà ïîèñêà ìîäóëåé â ëîêàëüíîé ñåòè + + Ïðè âûçîâå äàííîé ôóíêöèè çàïóñêàåòñÿ ïðîöåññ ïîèñêà ñåðâèñîâ, ñîîòâåòñòâóþùèõ + ìîäóëÿì E-502, â ëîêàëüíîé ñåòè è ñîçäàåòñÿ êîíòåêñò òåêóùåãî ñåàíñà ïîèñêà. + Ýòîò êîíòåêñò èñïîëüçóåòñÿ äëÿ äàëüíåéøèõ âûçîâîâ E502_EthSvcBrowseGetEvent(). + Ïîñëå çàâåðøåíèÿ ïîèñêà äîëæíà áûòü âûçâàíà ôóíêöèÿ E502_EthSvcBrowseStop(). + Äëÿ çàïóñêà ñåàíñà íåîáõîäèìà çàïóùåííàÿ ñëóæáà (äåìîí) îáíàðóæåíèÿ --- + ïîääåðæèâàþòñÿ Bonjour äëÿ ÎÑ Windows è Avahi äëÿ ÎÑ Linux. + @param[out] pcontext Óêàçàòåëü, â êîòîðûé ïðè óñïåøíîì âûïîëíåíèè + ñîõðàíÿåòñÿ êîíòåêñò ñåàíñà ïîèñêà óñòðîéñòâ. + @param[in] flags Ôëàãè (ðåçåðâ). Äîëæåí âñåãäà ïåðåäàâàòüñÿ 0. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) E502_EthSvcBrowseStart(t_e502_eth_svc_browse_hnd *pcontext, + uint32_t flags); + +/***************************************************************************//** + @brief Ïîëó÷åíèå èíôîðìàöèè î èçìåíåíèè ïðèñóòñòâèÿ ìîäóëåé â ëîêàëüíîé ñåòè + + Äàííàÿ ôóíêöèÿ ïîçâîëÿåò êàê ïîëó÷èòü ñïèñîê ïðèñóòñòâóþùèõ ìîäóëåé + (ñåòåâûõ ñåðâèñîâ) â ëîêàëüíîé ñåòè, òàê è îòñëåæèâàòü + â äàëüíåéøåì èçìåíåíèå èõ ñîñòîÿíèÿ. + + Ôóíêöèÿ æäåò ïåðâîãî èçìåíåíèÿ ñîñòîÿíèÿ è âîçâðàùàåò èíôîðìàöèþ î íåì. + Èíôîðìàöèÿ ñîñòîèò èç ñîáûòèÿ (ïîÿâëåíèå ñåòåâîãî ñåðâèñà, èñ÷åçíîâåíèå, + èçìåíåíèå ïàðàìåòðîâ) è èç îïèñàòåëÿ ñåòåâîãî ñåðâèñà, êîòîðîìó ñîîòâåòñòâóåò ñîáûòèå. + + Ïîñëå íà÷àëà ïîèñêà ñ ïîìîùüþ E502_EthSvcBrowseStart() êîíòåêñò íå ñîäåðæèò + èíôîðìàöèè î íàëè÷èå ñåòåâûõ ñåðâèñîâ. Åñëè óæå åñòü ïîäêëþ÷åííûå â ëîêàëüíîé ñåòè + ìîäóëè E-502, òî èíôîðìàöèÿ î íèõ áóäåò âîçâðàùåíà â ñëåäóþùèõ + E502_EthSvcBrowseGetEvent() ñ ñîáûòèåì #E502_ETH_SVC_EVENT_ADD, çà + êàæäûé âûçîâ ïî îäíîìó óñòðîéñòâó. + + Åñëè çà çàäàííûé òàéìàóò íå ïðîèçîøëî íè îäíîãî èçìåíåíèÿ, òî ôóíêöèÿ + çàâåðøèòñÿ óñïåøíî ïî îêîí÷àíèþ òàéìàóòà è âåðíåò ñîáûòèå #E502_ETH_SVC_EVENT_NONE. + + Ïðè æåëàíèè ìîæíî ïðîäîëæàòü âûçâàòü äàííóþ ôóíêöèþ äëÿ íåïðåðûâíîãî îòñëåæèâàíèÿ + ñîñòîÿíèÿ ïîäêëþ÷åíèÿ ìîäóëåé. + + @param[in] context Îïèñàòåëü êîíòåêñòà ïîèñêà, ñîçäàííûé ïðè âûçîâå + E502_EthSvcBrowseStart(). + @param[out] svc Åñëè âîçâðàùåííîå ñîáûòèå íå ðàâíî #E502_ETH_SVC_EVENT_NONE, + òî â äàííîé ïåðåìåííîé ñîõðàíÿåòñÿ ñîçäàííûé îïèñàòåëü + ñåòåâîãî ñåðâèñà, ñîîòâåòñòâóþùåãî óêàçàííîìó ñîáûòèþ. Ýòîò + îïèñàòåëü äîëæåí áûòü âñåãäà óíè÷òîæåí âðó÷íóþ + ñ ïîìîùüþ E502_EthSvcRecordFree(). + @param[out] event  äàííóþ ïåðåìåííóþ ñîõðàíÿåòñÿ êîä ñîáûòèÿ (îäèí èç + #t_e502_eth_svc_event). Åñëè çà óêàçàííîå âðåìÿ íå + ïðîèçîøëî íè îäíîãî ñîáûòèÿ, òî âîçâðàùàåòñÿ êîä + #E502_ETH_SVC_EVENT_NONE. + @param[out] flags  äàííîé ïåðåìåííîé ñîõðàíÿþòñÿ äîïîëíèòåëüíûå êîäû + ôëàãîâ (ðåçåðâ). Ìîæåò áûòü ïåðåäàí íóëåâîé óêàçàòåëü, + åñëè çíà÷åíèå ôëàãîâ íå èíòåðåñóåò. + @param[in] tout Òàéìàóò (â ìñ) íà âðåìÿ îæèäàíèÿ ñîáûòèÿ + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) E502_EthSvcBrowseGetEvent(t_e502_eth_svc_browse_hnd context, + t_e502_eth_svc_record_hnd *svc , + uint32_t *event, uint32_t *flags, + uint32_t tout); + +/***************************************************************************//** + @brief Îñòàíîâ ñåàíñà ïîèñêà ìîäóëåé â ëîêàëüíîé ñåòè + + Ïðè âûçîâå äàííîé ôóíêöèè ïðîöåññ ïîèñêà ñåòåâûõ ñåðâèñîâ, ñîîòâåòñòâóþùèé + óêàçàííîìó êîíòåêñòó, îñòàíàâëèâàåòñÿ. Âñå ðåñóðñû, âûäåëåííûå íà ýòàïå + E502_EthSvcBrowseStart() îñâîáîæäàþòñÿ. Êîíòåêñò ñ ýòîãî ìîìåíòà ñòàíîâèòñÿ + íåäåéñòâèòåëüíûì. + Âûçîâó E502_EthSvcBrowseStart() âñåãäà äîëæåí ñîîòâåòñòâîâàòü ïîñëåäóþùèé + âûçîâ E502_EthSvcBrowseStop() äëÿ êîððåêòíîãî îñâîáîæäåíèÿ ðåñóðñîâ. + + @param[in] context Îïèñàòåëü êîíòåêñòà ïîèñêà, ñîçäàííûé ïðè âûçîâå + E502_EthSvcBrowseStart(). + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) E502_EthSvcBrowseStop(t_e502_eth_svc_browse_hnd context); + + + +/***************************************************************************//** + @brief Îñâîáîæäåíèå îïèñàòåëÿ ñåòåâîãî ñåðâèñà + + Îñâîáîæäåíèå ïàìÿòè, âûäåëåííîé ïîä îïèñàòåëü ñåòåâîãî ñåðâèñà ïðè âûçîâå + E502_EthSvcBrowseGetEvent(). + + @param[in] svc Îïèñàòåëü ñåòåâîãî ñåðâèñà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthSvcRecordFree(t_e502_eth_svc_record_hnd svc); + +/***************************************************************************//** + @brief Ïîëó÷èòü èìÿ ýêçåìïëÿðà ïî îïèñàòåëþ ñåðâèñà + + Ôóíêöèÿ âîçâðàùàåò èìÿ ýêçåìïëÿðà ñåðâèñà. Ýòî èìÿ ñîîòâåòñòâóåò èìåíè, + êîòîðîå óñòàíîâëåíî â ñåòåâûõ íàñòðîéêàõ ìîäóëÿ, ñîîòâåòñòâóþùåãî óêàçàííîìó + ñåðâèñó, ñ ïîìîùüþ E502_EthConfigSetInstanceName(). + Ñëåäóåò îòìåòèòü, ÷òî äàííîå èìÿ, â îòëè÷èå îò îñòàëüíûõ ñòðîê, ïðåäñòàâëåíî + â êîäèðîâêå UTF-8, êîòîðàÿ ñîâïàäàåò ñ îáû÷íîé ASCII ñòðîêîé òîëüêî äëÿ + ñèìâîëîâ àíãëèéñêîãî àëôàâèòà. + Ôóíêöèÿ íå âûïîëíÿåò çàïðîñîâ ê ñàìîìó ìîäóëþ. + + @param[in] svc Îïèñàòåëü ñåòåâîãî ñåðâèñà + @param[out] name Ìàññèâ íà #X502_INSTANCE_NAME_SIZE áàéò, â êîòîðûé áóäåò + ñîõðàíåíî íàçâàíèå ýêçåìïëÿðà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthSvcRecordGetInstanceName(t_e502_eth_svc_record_hnd svc, char *name); + +/***************************************************************************//** + @brief Ïîëó÷èòü ñåðèéíûé íîìåð ìîäóëÿ ïî îïèñàòåëþ ñåòåâîãî ñåðâèñà + + Ôóíêöèÿ âîçâðàùàåò ñåðèéíûé íîìåð ìîäóëÿ E-502, ñîîòâåòñòâóþùåãî ñåòåâîìó + ñåðâèñó, íà êîòîðûé óêàçûâàåò ïåðåäàííûé îïèñàòåëü. + Ôóíêöèÿ íå âûïîëíÿåò çàïðîñîâ ê ñàìîìó ìîäóëþ. + + @param[in] svc Îïèñàòåëü ñåòåâîãî ñåðâèñà + @param[out] serial Ìàññèâ íà #X502_SERIAL_SIZE áàéò, â êîòîðûé áóäåò + ñîõðàíåí ñåðèéíûé íîìåð + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthSvcRecordGetDevSerial(t_e502_eth_svc_record_hnd svc, char *serial); + +/***************************************************************************//** + @brief Ïîëó÷èòü èìÿ óñðîéñòâà ìîäóëÿ ïî îïèñàòåëþ ñåòåâîãî ñåðâèñà + + Ôóíêöèÿ âîçâðàùàåò èìÿ óñòðîéñòâà ìîäóëÿ ("E502" èëè "E16"), ñîîòâåòñòâóþùåãî ñåòåâîìó + ñåðâèñó, íà êîòîðûé óêàçûâàåò ïåðåäàííûé îïèñàòåëü. + Ôóíêöèÿ íå âûïîëíÿåò çàïðîñîâ ê ñàìîìó ìîäóëþ. + + @param[in] svc Îïèñàòåëü ñåòåâîãî ñåðâèñà + @param[out] devname Ìàññèâ íà #X502_DEVNAME_SIZE áàéò, â êîòîðûé áóäåò + ñîõðàíåíî èìÿ óñòðîéñòâà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthSvcRecordGetDevName(t_e502_eth_svc_record_hnd rec, char *devname); + + +/***************************************************************************//** + @brief Ïîëó÷èòü IP àäðåñ ñåòåâîãî ñåðâèñà + + Ôóíêöèÿ ïîëó÷àåò IP-àäðåñ ìîäóëÿ E-502, ñîîòâåòñòâóþùåãî ñåòåâîìó + ñåðâèñó, íà êîòîðûé óêàçûâàåò ïåðåäàííûé îïèñàòåëü. Ôóíêöèÿ ïðè íåîáõîäèìîñòè + ìîæåò âûïîëíÿòü çàïðîñû ê ñàìîìó ìîäóëþ äëÿ ïîëó÷åíèÿ ýòîãî àäðåñà, åñëè + èíôîðìàöèè î àäðåñå íåò â êåøå. + + @param[in] svc Îïèñàòåëü ñåòåâîãî ñåðâèñà + @param[out] addr IP-àäðåñ ìîäóëÿ â âèäå 32-áèòíîãî ñëîâà (àíàëîãè÷íî + ïàðàìåòðó ip_addr ôóíêöèè E502_OpenByIpAddr()) + @param[in] tout Âðåìÿ îæèäàíèÿ îòâåòà îò ìîäóëÿ â ñëó÷àå íåîáõîäèìîñòè + âûïîëíèòü çàïðîñ äëÿ óñòàíîâëåíèÿ àäðåñà. + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthSvcRecordResolveIPv4Addr(t_e502_eth_svc_record_hnd svc, + uint32_t *addr, uint32_t tout); + + +/***************************************************************************//** + @brief Ïðîâåðêà, óêàçûâàþò ëè îáà îïèñàòåëÿ íà îäèí ýêçåìïëÿð ñåðâèñà + + Ôóíêöèÿ ïðîâåðÿåò, óêàçûâàþò ëè îáà îïèñàòåëÿ ñåðâèñîâ íà îäèí è òîò æå + ýêçåìïëÿð. Åñëè ïðèëîæåíèå ñîõðàíÿåò ñïèñîê îïèñàòåëåé ñåðâèñîâ ïðè èõ + îáíàðóæåíèè, òî äàííàÿ ôóíêöèÿ ìîæåò èñïîëüçîâàòüñÿ, íàïðèìåð, ïðè ñîáûòèÿõ + #E502_ETH_SVC_EVENT_REMOVE èëè #E502_ETH_SVC_EVENT_CHANGED, ÷òîáû ïîíÿòü, + êàêîé çàïèñè â ñîõðàíåííîì ñïèñêå ñîîòâåòñòâóåò ñîáûòèå (ò.å. ôóíêöèÿ + E502_EthSvcBrowseGetEvent() âåðíåò íîâûé îïèñàòåëü, íî óêàçûâàþùèé íà òîò + æå ýêçåìïëÿð, ÷òî è ïðè ñîáûòèè #E502_ETH_SVC_EVENT_ADD) + + @param[in] svc1 Ïåðâûé îïèñàòåëü ñåòåâîãî ñåðâèñà äëÿ ñðàâíåíèÿ + @param[in] svc2 Âòîðîé îïèñàòåëü ñåòåâîãî ñåðâèñà äëÿ ñðàâíåíèÿ + @return Êîä îøèáêè. Âîçâðàùàåò #X502_ERR_OK, åñëè îáà îïèñàòåëÿ + óêàçûâàþò íà îäèí ýêçåìïëÿð. +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthSvcRecordIsSameInstance(t_e502_eth_svc_record_hnd svc1, + t_e502_eth_svc_record_hnd svc2); + + +/***************************************************************************//** + @brief Ïîëó÷èòü ñïèñîê çàïèñåé, ïðèñóòñòâóþùèõ â ëîêàëüíîé ñåòè + + Ôóíêöèÿ ÿâëÿåòñÿ îáåðòêîé E502_EthSvcBrowse...() + Ôóíêöèÿ íàõîäèò âñå ïîäêëþ÷åííûå ïî èíòåðôåéñó Ethernet ìîäóëè è èíèöèàëèçèðóåò + çàïèñè î êàæäîì íàéäåííîì óñòðîéñòâå è ñîõðàíÿåò èõ â ïåðåäàííûé ñïèñîê + (åñëè íå íóëåâîé). + Âîçâðàùåííûå â ñïèñêå çàïèñè äîëæíû áûòü î÷èùåíû ïîñëå èñïîëüçîâàíèÿ + ñ ïîìîùüþ X502_FreeDevRecordList(). + + @param[out] list Ìàññèâ äëÿ ñîõðàíåíèÿ çàïèñåé î íàéäåííûõ óñòðîéñòâàõ. + Äîëæåí ñîäåðæàòü ìåñòî äëÿ ñîõðàíåíèÿ íå ìåíåå size çàïèñåé. + Ìîæåò áûòü NULL, åñëè size=0, à devcnt!=NULL, â ñëó÷àå, + åñëè íóæíî òîëüêî ïîëó÷èòü êîëè÷åñòâî ìîäóëåé â ñèñòåìå. + @param[in] size Îïðåäåëÿåò, ñêîëüêî ìàêñèìàëüíî çàïèñåé ìîæåò + áûòü ñîõðàíåíî â ìàññèâ list. Áóäóò ñîõðàíåíû òîëüêî + ïåðâûå size çàïèñåé, åñëè óñòðîéñòâ íàéäåíî áîëüøå. + @param[in] flags Ôëàãè èç #t_x502_getdevs_flags, îïðåäåëÿþùèå ïîâåäåíèå + ôóíêöèè. + @param[out] devcnt Åñëè íå íóëåâîé óêàçàòåëü, òî â äàííóþ ïåðåìåííóþ ñîõðàíÿåòñÿ + îáùåå ÷èñëî íàéäåííûõ ìîäóëåé, ïîäêëþ÷åííûõ ïî Ethernet (ìîæåò áûòü áîëüøå size). + @param[in] event_tout Âðåìÿ íà âðåìÿ îæèäàíèÿ ñîáûòèÿ â ìñ ôóíêöèè E502_EthSvcBrowseGetEvent() + @param[in] tcp_tout Âðåìÿ äëÿ óñòàíîâëåíèÿ ïîäêëþ÷åíèÿ â ìñ ôóíêöèè E502_MakeDevRecordByEthSvc() + @return Åñëè <0 --- êîä îøèáêè, èíà÷å êîëè÷åñòâî ñîõðàíåííûõ + çàïèñåé î íàéäåííûõ óñòðîéñòâàõ (âñåãäà <= size). + Èìåííî íà ýòîò ðàçìåð íóæíî ñäåëàòü â äàëüíåéøåì + X502_FreeDevRecordList() äëÿ îñâîáîæäåíèÿ ïàìÿòè, + âûäåëåííîé ïîä èíôîðìàöèþ, íà êîòîðóþ ññûëàåòñÿ çàïèñü. + ******************************************************************************/ +X502_EXPORT(int32_t) E502_SearchEthForDevicesIPv4Addr(t_x502_devrec* rec_list, uint32_t flags, uint32_t size, + uint32_t *devcount, uint32_t event_tout, uint32_t tcp_tout); + +typedef int32_t (APIENTRY *t_x502_search_eth_cb)(t_e502_eth_svc_record_hnd svc_hnd, void* param); + + +/***************************************************************************//** + @brief Ïîëó÷èòü ñïèñîê çàïèñåé, ïðèñóòñòâóþùèõ â ëîêàëüíîé ñåòè + + Ôóíêöèÿ ÿâëÿåòñÿ îáåðòêîé E502_EthSvcBrowse...() + Ôóíêöèÿ íàõîäèò âñå ïîäêëþ÷åííûå ïî èíòåðôåéñó Ethernet ìîäóëè è âûçûâàåò + ôóíêöèþ cb t_x502_search_eth_cb + + @param[in] cb Óêàçàòåëü íà callback-ôóíêöèþ, êîòîðàÿ áóäåò âûçâàíà ñ íîâûì íàéäåííûì ýêçåìïëÿðîì. + @param[in] param Ïàðàìåòð äëÿ âûçîâà callback-ôóíêöèè. + @param[in] flags Ôëàãè èç #t_x502_getdevs_flags, îïðåäåëÿþùèå ïîâåäåíèå + ôóíêöèè. + @param[in] event_tout Âðåìÿ íà âðåìÿ îæèäàíèÿ ñîáûòèÿ â ìñ ôóíêöèè E502_EthSvcBrowseGetEvent() + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) E502_SvcBrowseForDevices(t_x502_search_eth_cb cb, void *param, uint32_t flags, uint32_t event_tout); + +/***************************************************************************//** + @brief Óñòàíîâêà íîìåðà tcp ïîðòà äëÿ êîìàíä + + Åñëè íîìåð ïîðòà ïî óìîë÷àíèþ íå ïîäõîäèò, åãî ìîæíî ïîìåíÿòü ýòîé ôóíêöèåé + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + @param[in] port Íîìåð ïîðòà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigSetTcpCmdPort(t_e502_eth_config_hnd cfg, uint16_t port); +/***************************************************************************//** + @brief Óñòàíîâêà íîìåðà tcp ïîðòà äëÿ äàííûõ + + Åñëè íîìåð ïîðòà ïî óìîë÷àíèþ íå ïîäõîäèò, åãî ìîæíî ïîìåíÿòü ýòîé ôóíêöèåé + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + @param[in] port Íîìåð ïîðòà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigSetTcpDataPort(t_e502_eth_config_hnd cfg, uint16_t port); +/***************************************************************************//** + @brief Ïîëó÷åíèå íîìåðà tcp ïîðòà äëÿ êîìàíä + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + @param[out] port Óêàçàòåëü, êóäà çàïèøåòñÿ íîìåð ïîðòà â ñëó÷àå óñïåõà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigGetTcpCmdPort(t_e502_eth_config_hnd cfg, uint16_t *port); +/***************************************************************************//** + @brief Ïîëó÷åíèå íîìåðà tcp ïîðòà äëÿ äàííûõ + + @param[in] cfg Îïèñàòåëü êîíôèãóðàöèè ñåòåâîãî èíòåðôåéñà + @param[out] port Óêàçàòåëü, êóäà çàïèøåòñÿ íîìåð ïîðòà â ñëó÷àå óñïåõà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) E502_EthConfigGetTcpDataPort(t_e502_eth_config_hnd cfg, uint16_t *port); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif // E502API_H diff --git a/data_acq_and_processing/kamil_adc_collector/vendor/lcard/lcard_pstdint.h b/data_acq_and_processing/kamil_adc_collector/vendor/lcard/lcard_pstdint.h new file mode 100644 index 0000000..c82f7e4 --- /dev/null +++ b/data_acq_and_processing/kamil_adc_collector/vendor/lcard/lcard_pstdint.h @@ -0,0 +1,810 @@ +/* A portable stdint.h + **************************************************************************** + * BSD License: + **************************************************************************** + * + * Copyright (c) 2005-2011 Paul Hsieh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************** + * + * Version 0.1.12 + * + * The ANSI C standard committee, for the C99 standard, specified the + * inclusion of a new standard include file called stdint.h. This is + * a very useful and long desired include file which contains several + * very precise definitions for integer scalar types that is + * critically important for making portable several classes of + * applications including cryptography, hashing, variable length + * integer libraries and so on. But for most developers its likely + * useful just for programming sanity. + * + * The problem is that most compiler vendors have decided not to + * implement the C99 standard, and the next C++ language standard + * (which has a lot more mindshare these days) will be a long time in + * coming and its unknown whether or not it will include stdint.h or + * how much adoption it will have. Either way, it will be a long time + * before all compilers come with a stdint.h and it also does nothing + * for the extremely large number of compilers available today which + * do not include this file, or anything comparable to it. + * + * So that's what this file is all about. Its an attempt to build a + * single universal include file that works on as many platforms as + * possible to deliver what stdint.h is supposed to. A few things + * that should be noted about this file: + * + * 1) It is not guaranteed to be portable and/or present an identical + * interface on all platforms. The extreme variability of the + * ANSI C standard makes this an impossibility right from the + * very get go. Its really only meant to be useful for the vast + * majority of platforms that possess the capability of + * implementing usefully and precisely defined, standard sized + * integer scalars. Systems which are not intrinsically 2s + * complement may produce invalid constants. + * + * 2) There is an unavoidable use of non-reserved symbols. + * + * 3) Other standard include files are invoked. + * + * 4) This file may come in conflict with future platforms that do + * include stdint.h. The hope is that one or the other can be + * used with no real difference. + * + * 5) In the current verison, if your platform can't represent + * int32_t, int16_t and int8_t, it just dumps out with a compiler + * error. + * + * 6) 64 bit integers may or may not be defined. Test for their + * presence with the test: #ifdef INT64_MAX or #ifdef UINT64_MAX. + * Note that this is different from the C99 specification which + * requires the existence of 64 bit support in the compiler. If + * this is not defined for your platform, yet it is capable of + * dealing with 64 bits then it is because this file has not yet + * been extended to cover all of your system's capabilities. + * + * 7) (u)intptr_t may or may not be defined. Test for its presence + * with the test: #ifdef PTRDIFF_MAX. If this is not defined + * for your platform, then it is because this file has not yet + * been extended to cover all of your system's capabilities, not + * because its optional. + * + * 8) The following might not been defined even if your platform is + * capable of defining it: + * + * WCHAR_MIN + * WCHAR_MAX + * (u)int64_t + * PTRDIFF_MIN + * PTRDIFF_MAX + * (u)intptr_t + * + * 9) The following have not been defined: + * + * WINT_MIN + * WINT_MAX + * + * 10) The criteria for defining (u)int_least(*)_t isn't clear, + * except for systems which don't have a type that precisely + * defined 8, 16, or 32 bit types (which this include file does + * not support anyways). Default definitions have been given. + * + * 11) The criteria for defining (u)int_fast(*)_t isn't something I + * would trust to any particular compiler vendor or the ANSI C + * committee. It is well known that "compatible systems" are + * commonly created that have very different performance + * characteristics from the systems they are compatible with, + * especially those whose vendors make both the compiler and the + * system. Default definitions have been given, but its strongly + * recommended that users never use these definitions for any + * reason (they do *NOT* deliver any serious guarantee of + * improved performance -- not in this file, nor any vendor's + * stdint.h). + * + * 12) The following macros: + * + * PRINTF_INTMAX_MODIFIER + * PRINTF_INT64_MODIFIER + * PRINTF_INT32_MODIFIER + * PRINTF_INT16_MODIFIER + * PRINTF_LEAST64_MODIFIER + * PRINTF_LEAST32_MODIFIER + * PRINTF_LEAST16_MODIFIER + * PRINTF_INTPTR_MODIFIER + * + * are strings which have been defined as the modifiers required + * for the "d", "u" and "x" printf formats to correctly output + * (u)intmax_t, (u)int64_t, (u)int32_t, (u)int16_t, (u)least64_t, + * (u)least32_t, (u)least16_t and (u)intptr_t types respectively. + * PRINTF_INTPTR_MODIFIER is not defined for some systems which + * provide their own stdint.h. PRINTF_INT64_MODIFIER is not + * defined if INT64_MAX is not defined. These are an extension + * beyond what C99 specifies must be in stdint.h. + * + * In addition, the following macros are defined: + * + * PRINTF_INTMAX_HEX_WIDTH + * PRINTF_INT64_HEX_WIDTH + * PRINTF_INT32_HEX_WIDTH + * PRINTF_INT16_HEX_WIDTH + * PRINTF_INT8_HEX_WIDTH + * PRINTF_INTMAX_DEC_WIDTH + * PRINTF_INT64_DEC_WIDTH + * PRINTF_INT32_DEC_WIDTH + * PRINTF_INT16_DEC_WIDTH + * PRINTF_INT8_DEC_WIDTH + * + * Which specifies the maximum number of characters required to + * print the number of that type in either hexadecimal or decimal. + * These are an extension beyond what C99 specifies must be in + * stdint.h. + * + * Compilers tested (all with 0 warnings at their highest respective + * settings): Borland Turbo C 2.0, WATCOM C/C++ 11.0 (16 bits and 32 + * bits), Microsoft Visual C++ 6.0 (32 bit), Microsoft Visual Studio + * .net (VC7), Intel C++ 4.0, GNU gcc v3.3.3 + * + * This file should be considered a work in progress. Suggestions for + * improvements, especially those which increase coverage are strongly + * encouraged. + * + * Acknowledgements + * + * The following people have made significant contributions to the + * development and testing of this file: + * + * Chris Howie + * John Steele Scott + * Dave Thorup + * John Dill + * + */ + +#ifndef LCARD_PSTDINT +#define LCARD_PSTDINT + +#include +#include + +/* + * For gcc with _STDINT_H, fill in the PRINTF_INT*_MODIFIER macros, and + * do nothing else. On the Mac OS X version of gcc this is _STDINT_H_. + */ + +#if ((defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) \ + || (defined (__WATCOMC__) && (defined (_STDINT_H_INCLUDED) || (__WATCOMC__ >= 1250))) \ + || (defined(__GNUC__)) \ + || (defined (_MSC_VER) && (_MSC_VER >= 1600)) \ + || (defined (__BORLANDC__) && (__BORLANDC__ >= 0x560))) && !defined (_PSTDINT_H_INCLUDED) +#include +#define _PSTDINT_H_INCLUDED +# ifndef PRINTF_INT64_MODIFIER +# define PRINTF_INT64_MODIFIER "ll" +# endif +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "l" +# endif +# ifndef PRINTF_INT16_MODIFIER +# define PRINTF_INT16_MODIFIER "h" +# endif +# ifndef PRINTF_INTMAX_MODIFIER +# define PRINTF_INTMAX_MODIFIER PRINTF_INT64_MODIFIER +# endif +# ifndef PRINTF_INT64_HEX_WIDTH +# define PRINTF_INT64_HEX_WIDTH "16" +# endif +# ifndef PRINTF_INT32_HEX_WIDTH +# define PRINTF_INT32_HEX_WIDTH "8" +# endif +# ifndef PRINTF_INT16_HEX_WIDTH +# define PRINTF_INT16_HEX_WIDTH "4" +# endif +# ifndef PRINTF_INT8_HEX_WIDTH +# define PRINTF_INT8_HEX_WIDTH "2" +# endif +# ifndef PRINTF_INT64_DEC_WIDTH +# define PRINTF_INT64_DEC_WIDTH "20" +# endif +# ifndef PRINTF_INT32_DEC_WIDTH +# define PRINTF_INT32_DEC_WIDTH "10" +# endif +# ifndef PRINTF_INT16_DEC_WIDTH +# define PRINTF_INT16_DEC_WIDTH "5" +# endif +# ifndef PRINTF_INT8_DEC_WIDTH +# define PRINTF_INT8_DEC_WIDTH "3" +# endif +# ifndef PRINTF_INTMAX_HEX_WIDTH +# define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT64_HEX_WIDTH +# endif +# ifndef PRINTF_INTMAX_DEC_WIDTH +# define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT64_DEC_WIDTH +# endif + +/* + * Something really weird is going on with Open Watcom. Just pull some of + * these duplicated definitions from Open Watcom's stdint.h file for now. + */ + +# if defined (__WATCOMC__) && __WATCOMC__ >= 1250 +# if !defined (INT64_C) +# define INT64_C(x) (x + (INT64_MAX - INT64_MAX)) +# endif +# if !defined (UINT64_C) +# define UINT64_C(x) (x + (UINT64_MAX - UINT64_MAX)) +# endif +# if !defined (INT32_C) +# define INT32_C(x) (x + (INT32_MAX - INT32_MAX)) +# endif +# if !defined (UINT32_C) +# define UINT32_C(x) (x + (UINT32_MAX - UINT32_MAX)) +# endif +# if !defined (INT16_C) +# define INT16_C(x) (x) +# endif +# if !defined (UINT16_C) +# define UINT16_C(x) (x) +# endif +# if !defined (INT8_C) +# define INT8_C(x) (x) +# endif +# if !defined (UINT8_C) +# define UINT8_C(x) (x) +# endif +# if !defined (UINT64_MAX) +# define UINT64_MAX 18446744073709551615ULL +# endif +# if !defined (INT64_MAX) +# define INT64_MAX 9223372036854775807LL +# endif +# if !defined (UINT32_MAX) +# define UINT32_MAX 4294967295UL +# endif +# if !defined (INT32_MAX) +# define INT32_MAX 2147483647L +# endif +# if !defined (INTMAX_MAX) +# define INTMAX_MAX INT64_MAX +# endif +# if !defined (INTMAX_MIN) +# define INTMAX_MIN INT64_MIN +# endif +# endif +#endif + +#ifndef _PSTDINT_H_INCLUDED +#define _PSTDINT_H_INCLUDED + +#ifndef SIZE_MAX +# define SIZE_MAX (~(size_t)0) +#endif + +/* + * Deduce the type assignments from limits.h under the assumption that + * integer sizes in bits are powers of 2, and follow the ANSI + * definitions. + */ + +#ifndef UINT8_MAX +# define UINT8_MAX 0xff +#endif +#ifndef uint8_t +# if (UCHAR_MAX == UINT8_MAX) || defined (S_SPLINT_S) + typedef unsigned char uint8_t; +# define UINT8_C(v) ((uint8_t) v) +# else +# error "Platform not supported" +# endif +#endif + +#ifndef INT8_MAX +# define INT8_MAX 0x7f +#endif +#ifndef INT8_MIN +# define INT8_MIN INT8_C(0x80) +#endif +#ifndef int8_t +# if (SCHAR_MAX == INT8_MAX) || defined (S_SPLINT_S) + typedef signed char int8_t; +# define INT8_C(v) ((int8_t) v) +# else +# error "Platform not supported" +# endif +#endif + +#ifndef UINT16_MAX +# define UINT16_MAX 0xffff +#endif +#ifndef uint16_t +#if (UINT_MAX == UINT16_MAX) || defined (S_SPLINT_S) + typedef unsigned int uint16_t; +# ifndef PRINTF_INT16_MODIFIER +# define PRINTF_INT16_MODIFIER "" +# endif +# define UINT16_C(v) ((uint16_t) (v)) +#elif (USHRT_MAX == UINT16_MAX) + typedef unsigned short uint16_t; +# define UINT16_C(v) ((uint16_t) (v)) +# ifndef PRINTF_INT16_MODIFIER +# define PRINTF_INT16_MODIFIER "h" +# endif +#else +#error "Platform not supported" +#endif +#endif + +#ifndef INT16_MAX +# define INT16_MAX 0x7fff +#endif +#ifndef INT16_MIN +# define INT16_MIN INT16_C(0x8000) +#endif +#ifndef int16_t +#if (INT_MAX == INT16_MAX) || defined (S_SPLINT_S) + typedef signed int int16_t; +# define INT16_C(v) ((int16_t) (v)) +# ifndef PRINTF_INT16_MODIFIER +# define PRINTF_INT16_MODIFIER "" +# endif +#elif (SHRT_MAX == INT16_MAX) + typedef signed short int16_t; +# define INT16_C(v) ((int16_t) (v)) +# ifndef PRINTF_INT16_MODIFIER +# define PRINTF_INT16_MODIFIER "h" +# endif +#else +#error "Platform not supported" +#endif +#endif + +#ifndef UINT32_MAX +# define UINT32_MAX (0xffffffffUL) +#endif +#ifndef uint32_t +#if (ULONG_MAX == UINT32_MAX) || defined (S_SPLINT_S) + typedef unsigned long uint32_t; +# define UINT32_C(v) v ## UL +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "l" +# endif +#elif (UINT_MAX == UINT32_MAX) + typedef unsigned int uint32_t; +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "" +# endif +# define UINT32_C(v) v ## U +#elif (USHRT_MAX == UINT32_MAX) + typedef unsigned short uint32_t; +# define UINT32_C(v) ((unsigned short) (v)) +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "" +# endif +#else +#error "Platform not supported" +#endif +#endif + +#ifndef INT32_MAX +# define INT32_MAX (0x7fffffffL) +#endif +#ifndef INT32_MIN +# define INT32_MIN INT32_C(0x80000000) +#endif +#ifndef int32_t +#if (LONG_MAX == INT32_MAX) || defined (S_SPLINT_S) + typedef signed long int32_t; +# define INT32_C(v) v ## L +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "l" +# endif +#elif (INT_MAX == INT32_MAX) + typedef signed int int32_t; +# define INT32_C(v) v +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "" +# endif +#elif (SHRT_MAX == INT32_MAX) + typedef signed short int32_t; +# define INT32_C(v) ((short) (v)) +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "" +# endif +#else +#error "Platform not supported" +#endif +#endif + +/* + * The macro stdint_int64_defined is temporarily used to record + * whether or not 64 integer support is available. It must be + * defined for any 64 integer extensions for new platforms that are + * added. + */ + +#undef stdint_int64_defined +#if (defined(__STDC__) && defined(__STDC_VERSION__)) || defined (S_SPLINT_S) +# if (__STDC__ && __STDC_VERSION__ >= 199901L) || defined (S_SPLINT_S) +# define stdint_int64_defined + typedef long long int64_t; + typedef unsigned long long uint64_t; +# define UINT64_C(v) v ## ULL +# define INT64_C(v) v ## LL +# ifndef PRINTF_INT64_MODIFIER +# define PRINTF_INT64_MODIFIER "ll" +# endif +# endif +#endif + +#if !defined (stdint_int64_defined) +# if defined(__GNUC__) +# define stdint_int64_defined + __extension__ typedef long long int64_t; + __extension__ typedef unsigned long long uint64_t; +# define UINT64_C(v) v ## ULL +# define INT64_C(v) v ## LL +# ifndef PRINTF_INT64_MODIFIER +# define PRINTF_INT64_MODIFIER "ll" +# endif +# elif defined(__MWERKS__) || defined (__SUNPRO_C) || defined (__SUNPRO_CC) || defined (__APPLE_CC__) || defined (_LONG_LONG) || defined (_CRAYC) || defined (S_SPLINT_S) +# define stdint_int64_defined + typedef long long int64_t; + typedef unsigned long long uint64_t; +# define UINT64_C(v) v ## ULL +# define INT64_C(v) v ## LL +# ifndef PRINTF_INT64_MODIFIER +# define PRINTF_INT64_MODIFIER "ll" +# endif +# elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined (__BORLANDC__) && __BORLANDC__ > 0x460) || defined (__alpha) || defined (__DECC) +# define stdint_int64_defined + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; +# define UINT64_C(v) v ## UI64 +# define INT64_C(v) v ## I64 +# ifndef PRINTF_INT64_MODIFIER +# define PRINTF_INT64_MODIFIER "I64" +# endif +# endif +#endif + +#if !defined (LONG_LONG_MAX) && defined (INT64_C) +# define LONG_LONG_MAX INT64_C (9223372036854775807) +#endif +#ifndef ULONG_LONG_MAX +# define ULONG_LONG_MAX UINT64_C (18446744073709551615) +#endif + +#if !defined (INT64_MAX) && defined (INT64_C) +# define INT64_MAX INT64_C (9223372036854775807) +#endif +#if !defined (INT64_MIN) && defined (INT64_C) +# define INT64_MIN INT64_C (-9223372036854775808) +#endif +#if !defined (UINT64_MAX) && defined (INT64_C) +# define UINT64_MAX UINT64_C (18446744073709551615) +#endif + +/* + * Width of hexadecimal for number field. + */ + +#ifndef PRINTF_INT64_HEX_WIDTH +# define PRINTF_INT64_HEX_WIDTH "16" +#endif +#ifndef PRINTF_INT32_HEX_WIDTH +# define PRINTF_INT32_HEX_WIDTH "8" +#endif +#ifndef PRINTF_INT16_HEX_WIDTH +# define PRINTF_INT16_HEX_WIDTH "4" +#endif +#ifndef PRINTF_INT8_HEX_WIDTH +# define PRINTF_INT8_HEX_WIDTH "2" +#endif + +#ifndef PRINTF_INT64_DEC_WIDTH +# define PRINTF_INT64_DEC_WIDTH "20" +#endif +#ifndef PRINTF_INT32_DEC_WIDTH +# define PRINTF_INT32_DEC_WIDTH "10" +#endif +#ifndef PRINTF_INT16_DEC_WIDTH +# define PRINTF_INT16_DEC_WIDTH "5" +#endif +#ifndef PRINTF_INT8_DEC_WIDTH +# define PRINTF_INT8_DEC_WIDTH "3" +#endif + +/* + * Ok, lets not worry about 128 bit integers for now. Moore's law says + * we don't need to worry about that until about 2040 at which point + * we'll have bigger things to worry about. + */ + +#ifdef stdint_int64_defined + typedef int64_t intmax_t; + typedef uint64_t uintmax_t; +# define INTMAX_MAX INT64_MAX +# define INTMAX_MIN INT64_MIN +# define UINTMAX_MAX UINT64_MAX +# define UINTMAX_C(v) UINT64_C(v) +# define INTMAX_C(v) INT64_C(v) +# ifndef PRINTF_INTMAX_MODIFIER +# define PRINTF_INTMAX_MODIFIER PRINTF_INT64_MODIFIER +# endif +# ifndef PRINTF_INTMAX_HEX_WIDTH +# define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT64_HEX_WIDTH +# endif +# ifndef PRINTF_INTMAX_DEC_WIDTH +# define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT64_DEC_WIDTH +# endif +#else + typedef int32_t intmax_t; + typedef uint32_t uintmax_t; +# define INTMAX_MAX INT32_MAX +# define UINTMAX_MAX UINT32_MAX +# define UINTMAX_C(v) UINT32_C(v) +# define INTMAX_C(v) INT32_C(v) +# ifndef PRINTF_INTMAX_MODIFIER +# define PRINTF_INTMAX_MODIFIER PRINTF_INT32_MODIFIER +# endif +# ifndef PRINTF_INTMAX_HEX_WIDTH +# define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT32_HEX_WIDTH +# endif +# ifndef PRINTF_INTMAX_DEC_WIDTH +# define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT32_DEC_WIDTH +# endif +#endif + +/* + * Because this file currently only supports platforms which have + * precise powers of 2 as bit sizes for the default integers, the + * least definitions are all trivial. Its possible that a future + * version of this file could have different definitions. + */ + +#ifndef stdint_least_defined + typedef int8_t int_least8_t; + typedef uint8_t uint_least8_t; + typedef int16_t int_least16_t; + typedef uint16_t uint_least16_t; + typedef int32_t int_least32_t; + typedef uint32_t uint_least32_t; +# define PRINTF_LEAST32_MODIFIER PRINTF_INT32_MODIFIER +# define PRINTF_LEAST16_MODIFIER PRINTF_INT16_MODIFIER +# define UINT_LEAST8_MAX UINT8_MAX +# define INT_LEAST8_MAX INT8_MAX +# define UINT_LEAST16_MAX UINT16_MAX +# define INT_LEAST16_MAX INT16_MAX +# define UINT_LEAST32_MAX UINT32_MAX +# define INT_LEAST32_MAX INT32_MAX +# define INT_LEAST8_MIN INT8_MIN +# define INT_LEAST16_MIN INT16_MIN +# define INT_LEAST32_MIN INT32_MIN +# ifdef stdint_int64_defined + typedef int64_t int_least64_t; + typedef uint64_t uint_least64_t; +# define PRINTF_LEAST64_MODIFIER PRINTF_INT64_MODIFIER +# define UINT_LEAST64_MAX UINT64_MAX +# define INT_LEAST64_MAX INT64_MAX +# define INT_LEAST64_MIN INT64_MIN +# endif +#endif +#undef stdint_least_defined + +/* + * The ANSI C committee pretending to know or specify anything about + * performance is the epitome of misguided arrogance. The mandate of + * this file is to *ONLY* ever support that absolute minimum + * definition of the fast integer types, for compatibility purposes. + * No extensions, and no attempt to suggest what may or may not be a + * faster integer type will ever be made in this file. Developers are + * warned to stay away from these types when using this or any other + * stdint.h. + */ + +typedef int_least8_t int_fast8_t; +typedef uint_least8_t uint_fast8_t; +typedef int_least16_t int_fast16_t; +typedef uint_least16_t uint_fast16_t; +typedef int_least32_t int_fast32_t; +typedef uint_least32_t uint_fast32_t; +#define UINT_FAST8_MAX UINT_LEAST8_MAX +#define INT_FAST8_MAX INT_LEAST8_MAX +#define UINT_FAST16_MAX UINT_LEAST16_MAX +#define INT_FAST16_MAX INT_LEAST16_MAX +#define UINT_FAST32_MAX UINT_LEAST32_MAX +#define INT_FAST32_MAX INT_LEAST32_MAX +#define INT_FAST8_MIN INT_LEAST8_MIN +#define INT_FAST16_MIN INT_LEAST16_MIN +#define INT_FAST32_MIN INT_LEAST32_MIN +#ifdef stdint_int64_defined + typedef int_least64_t int_fast64_t; + typedef uint_least64_t uint_fast64_t; +# define UINT_FAST64_MAX UINT_LEAST64_MAX +# define INT_FAST64_MAX INT_LEAST64_MAX +# define INT_FAST64_MIN INT_LEAST64_MIN +#endif + +#undef stdint_int64_defined + +/* + * Whatever piecemeal, per compiler thing we can do about the wchar_t + * type limits. + */ + +#if defined(__WATCOMC__) || defined(_MSC_VER) || defined (__GNUC__) +# include +# ifndef WCHAR_MIN +# define WCHAR_MIN 0 +# endif +# ifndef WCHAR_MAX +# define WCHAR_MAX ((wchar_t)-1) +# endif +#endif + +/* + * Whatever piecemeal, per compiler/platform thing we can do about the + * (u)intptr_t types and limits. + */ + +#if defined (_MSC_VER) && defined (_UINTPTR_T_DEFINED) +# define STDINT_H_UINTPTR_T_DEFINED +#elif defined (_CVI_) +# define STDINT_H_UINTPTR_T_DEFINED +#endif + +#ifndef STDINT_H_UINTPTR_T_DEFINED +# if defined (__alpha__) || defined (__ia64__) || defined (__x86_64__) || defined (_WIN64) +# define stdint_intptr_bits 64 +# elif defined (__WATCOMC__) || defined (__TURBOC__) +# if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__) +# define stdint_intptr_bits 16 +# else +# define stdint_intptr_bits 32 +# endif +# elif defined (__i386__) || defined (_WIN32) || defined (WIN32) +# define stdint_intptr_bits 32 +# elif defined (__INTEL_COMPILER) +/* TODO -- what did Intel do about x86-64? */ +# endif + +# ifdef stdint_intptr_bits +# define stdint_intptr_glue3_i(a,b,c) a##b##c +# define stdint_intptr_glue3(a,b,c) stdint_intptr_glue3_i(a,b,c) +# ifndef PRINTF_INTPTR_MODIFIER +# define PRINTF_INTPTR_MODIFIER stdint_intptr_glue3(PRINTF_INT,stdint_intptr_bits,_MODIFIER) +# endif +# ifndef PTRDIFF_MAX +# define PTRDIFF_MAX stdint_intptr_glue3(INT,stdint_intptr_bits,_MAX) +# endif +# ifndef PTRDIFF_MIN +# define PTRDIFF_MIN stdint_intptr_glue3(INT,stdint_intptr_bits,_MIN) +# endif +# ifndef UINTPTR_MAX +# define UINTPTR_MAX stdint_intptr_glue3(UINT,stdint_intptr_bits,_MAX) +# endif +# ifndef INTPTR_MAX +# define INTPTR_MAX stdint_intptr_glue3(INT,stdint_intptr_bits,_MAX) +# endif +# ifndef INTPTR_MIN +# define INTPTR_MIN stdint_intptr_glue3(INT,stdint_intptr_bits,_MIN) +# endif +# ifndef INTPTR_C +# define INTPTR_C(x) stdint_intptr_glue3(INT,stdint_intptr_bits,_C)(x) +# endif +# ifndef UINTPTR_C +# define UINTPTR_C(x) stdint_intptr_glue3(UINT,stdint_intptr_bits,_C)(x) +# endif + typedef stdint_intptr_glue3(uint,stdint_intptr_bits,_t) uintptr_t; + typedef stdint_intptr_glue3( int,stdint_intptr_bits,_t) intptr_t; +# else +/* TODO -- This following is likely wrong for some platforms, and does + nothing for the definition of uintptr_t. */ + typedef ptrdiff_t intptr_t; +# endif +# define STDINT_H_UINTPTR_T_DEFINED +#endif + +/* + * Assumes sig_atomic_t is signed and we have a 2s complement machine. + */ + +#ifndef SIG_ATOMIC_MAX +# define SIG_ATOMIC_MAX ((((sig_atomic_t) 1) << (sizeof (sig_atomic_t)*CHAR_BIT-1)) - 1) +#endif + +#endif + +#if defined (__TEST_PSTDINT_FOR_CORRECTNESS) + +/* + * Please compile with the maximum warning settings to make sure macros are not + * defined more than once. + */ + +#include +#include +#include + +#define glue3_aux(x,y,z) x ## y ## z +#define glue3(x,y,z) glue3_aux(x,y,z) + +#define DECLU(bits) glue3(uint,bits,_t) glue3(u,bits,=) glue3(UINT,bits,_C) (0); +#define DECLI(bits) glue3(int,bits,_t) glue3(i,bits,=) glue3(INT,bits,_C) (0); + +#define DECL(us,bits) glue3(DECL,us,) (bits) + +#define TESTUMAX(bits) glue3(u,bits,=) glue3(~,u,bits); if (glue3(UINT,bits,_MAX) glue3(!=,u,bits)) printf ("Something wrong with UINT%d_MAX\n", bits) + +int main () { + DECL(I,8) + DECL(U,8) + DECL(I,16) + DECL(U,16) + DECL(I,32) + DECL(U,32) +#ifdef INT64_MAX + DECL(I,64) + DECL(U,64) +#endif + intmax_t imax = INTMAX_C(0); + uintmax_t umax = UINTMAX_C(0); + char str0[256], str1[256]; + + sprintf (str0, "%d %x\n", 0, ~0); + + sprintf (str1, "%d %x\n", i8, ~0); + if (0 != strcmp (str0, str1)) printf ("Something wrong with i8 : %s\n", str1); + sprintf (str1, "%u %x\n", u8, ~0); + if (0 != strcmp (str0, str1)) printf ("Something wrong with u8 : %s\n", str1); + sprintf (str1, "%d %x\n", i16, ~0); + if (0 != strcmp (str0, str1)) printf ("Something wrong with i16 : %s\n", str1); + sprintf (str1, "%u %x\n", u16, ~0); + if (0 != strcmp (str0, str1)) printf ("Something wrong with u16 : %s\n", str1); + sprintf (str1, "%" PRINTF_INT32_MODIFIER "d %x\n", i32, ~0); + if (0 != strcmp (str0, str1)) printf ("Something wrong with i32 : %s\n", str1); + sprintf (str1, "%" PRINTF_INT32_MODIFIER "u %x\n", u32, ~0); + if (0 != strcmp (str0, str1)) printf ("Something wrong with u32 : %s\n", str1); +#ifdef INT64_MAX + sprintf (str1, "%" PRINTF_INT64_MODIFIER "d %x\n", i64, ~0); + if (0 != strcmp (str0, str1)) printf ("Something wrong with i64 : %s\n", str1); +#endif + sprintf (str1, "%" PRINTF_INTMAX_MODIFIER "d %x\n", imax, ~0); + if (0 != strcmp (str0, str1)) printf ("Something wrong with imax : %s\n", str1); + sprintf (str1, "%" PRINTF_INTMAX_MODIFIER "u %x\n", umax, ~0); + if (0 != strcmp (str0, str1)) printf ("Something wrong with umax : %s\n", str1); + + TESTUMAX(8); + TESTUMAX(16); + TESTUMAX(32); +#ifdef INT64_MAX + TESTUMAX(64); +#endif + + return EXIT_SUCCESS; +} + +#endif + +#endif diff --git a/data_acq_and_processing/kamil_adc_collector/vendor/lcard/x502api.h b/data_acq_and_processing/kamil_adc_collector/vendor/lcard/x502api.h new file mode 100644 index 0000000..b775cb3 --- /dev/null +++ b/data_acq_and_processing/kamil_adc_collector/vendor/lcard/x502api.h @@ -0,0 +1,3129 @@ +#ifndef X502API_H +#define X502API_H + + + +#include "lcard_pstdint.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include "Windows.h" + + #define X502_EXPORT(type) type APIENTRY +#else + #ifndef APIENTRY + #define APIENTRY + #endif + + #if __GNUC__ >= 4 + #define X502_EXPORT(type) __attribute__ ((visibility("default"))) type + #else + #define X502_EXPORT(type) type + #endif +#endif + + +/***************************************************************************//** + @addtogroup const_list Êîíñòàíòû è ïåðå÷èñëåíèÿ + @{ + *****************************************************************************/ + +#define E502_DEVICE_NAME "E502" +#define L502_DEVICE_NAME "L502" +#define E16_DEVICE_NAME "E16" +#define LTA37_DEVICE_NAME "LTA37" +#define LTA11_DEVICE_NAME "LTA11" + +/** Ìàêñèìàëüíîå êîëè÷åñòâî ëîãè÷åñêèõ êàíàëîâ â òàáëèöå */ +#define X502_LTABLE_MAX_CH_CNT 256 + +/** Ìàêñèìàëüíîå êîëè÷åñòâî ëîãè÷åñêèõ êàíàëîâ â òàáëèöå äëÿ E16 */ +#define E16_LTABLE_MAX_CH_CNT 128 + +/** Êîëè÷åñòâî äèàïàçîíîâ äëÿ èçìåðåíèÿ íàïðÿæåíèé */ +#define X502_ADC_RANGE_CNT 6 +#define E16_ADC_RANGE_CNT 4 +#define LTA11_ADC_RANGE_CNT 4 + +/** Êîëè÷åñòâî êàíàëîâ ÀÖÏ â ðåæèìå ñ îáùåé çåìëåé */ +#define X502_ADC_COMM_CH_CNT 32 + +/** Êîëè÷åñòâî êàíàëîâ ÀÖÏ â äèôôåðåíöèàëüíîì ðåæèìå */ +#define X502_ADC_DIFF_CH_CNT 16 + + +/** Ìàêñèìàëüíîå çíà÷åíèå äëÿ àïïàðàòíîãî óñðåäíåíèÿ ïî ëîãè÷åñêîìó êàíàëó */ +#define X502_LCH_AVG_SIZE_MAX 128 +/** Ìàêñèìàëüíîå çíà÷åíèÿ äåëèòåëÿ ÷àñòîòû ÀÖÏ */ +#define X502_ADC_FREQ_DIV_MAX (1024*1024) +/** Ìàêñèìàëüíîå çíà÷åíèÿ äåëèòåëÿ ÷àñòîòû ÀÖÏ + *äëÿ ìîäóëÿ E16 ïî îòíîøåíèþ ê îïîðíîé ÷àñòîòå E16_REF_FREQ_48000KHZ */ +#define E16_ADC_FREQ_DIV_MAX (0x800000) +#define LTA11_ADC_FREQ_DIV_MAX (0x800000) +/** Ìàêñèìàëüíîå çíà÷åíèå äåëèòåëÿ ÷àñòîòû ñèíõðîííîãî öèôðîâîãî ââîäà */ +#define X502_DIN_FREQ_DIV_MAX (1024*1024) +/** Ìàêñèìàëüíîå çíà÷åíèå äåëèòåëÿ ÷àñòîòû ñèíõðîííîãî öèôðîâîãî ââîäà + *äëÿ ìîäóëÿ E16 ïî îòíîøåíèþ ê îïîðíîé ÷àñòîòå E16_REF_FREQ_48000KHZ */ +#define E16_DIN_FREQ_DIV_MAX (0x800000) +#define LTA37_DIN_FREQ_DIV_MAX 64 + +/** Çíà÷åíèÿ ÷àñòîòû ÀÖÏ ïî óìîë÷àíèþ, + * èñïîëüçóåòñÿ â ôóíêöèè X502_SetAdcFreq è X502_CalcAdcFreq2 åñëè ïàðàìåòð f_acq = 0 */ +#define E16_ADC_FREQ_DEFAULT 500000 +#define LTA11_ADC_FREQ_DEFAULT 500000 +#define LTA11_MAX_ADC_FREQ 1000000 + +/** Çíà÷åíèÿ ÷àñòîòû öèôðîâîãî âõîäà DIN ïî óìîë÷àíèþ, + * èñïîëüçóåòñÿ â ôóíêöèè X502_SetDinFreq è X502_CalcDinFreq2 åñëè ïàðàìåòð f_din = 0 */ +#define E16_DIN_FREQ_DEFAULT 500000 +/** Çíà÷åíèÿ ÷àñòîòû ÖÀÏ è öèôðîâûõ âûõîäîâ ïî óìîë÷àíèþ, + * èñïîëüçóåòñÿ â ôóíêöèè X502_SetOutFreq è X502_CalcOutFreq2 åñëè ïàðàìåòð f_dout = 0 */ +#define E16_OUT_FREQ_DEFAULT 500000 + + +/** Ìèíèìàëüíîå çíà÷åíèå äåëèòåëÿ ÷àñòîòû ñèíõðîííîãî âûâîäà */ +#define X502_OUT_FREQ_DIV_MIN 2 +/** Ìàêñèìàëüíîå çíà÷åíèå äåëèòåëÿ ÷àñòîòû ñèíõðîííîãî âûâîäà */ +#define X502_OUT_FREQ_DIV_MAX 1024 +/** Ìàêñèìàëüíîå çíà÷åíèå äåëèòåëÿ ÷àñòîòû ñèíõðîííîãî âûâîäà + *äëÿ ìîäóëÿ E16 ïî îòíîøåíèþ ê îïîðíîé ÷àñòîòå E16_REF_FREQ_48000KHZ */ +#define E16_OUT_FREQ_DIV_MAX 0x8000 +/** Çíà÷åíèå äåëèòåëÿ ÷àñòîòû âûâîäà ïî óìîë÷àíèþ (êîòîðîå òàêæå âñåãäà + èñïîëüçóåòñÿ â L-502 ñ âåðñèåé ïðîøèâêè ÏËÈÑ íèæå 0.5) */ +#define X502_OUT_FREQ_DIV_DEFAULT 2 + +/** Ìèíèìàëüíîå çíà÷åíèå äåëèòåëÿ ÷àñòîòû ñèíõðîííîãî âûâîäà + ïðè âíåøíåé îïîðíîé ÷àñòîòå íèæå #X502_OUT_FREQ_REF_LF_VAL */ +#define X502_OUT_FREQ_DIV_MIN_REF_LF 1 +/** Çíà÷åíèå âíåøíåé îïîðíîé ÷àñòîòû, íèæå êîòîðîé ðàçðåøåíî óñòàíàâëèâàòü + çíà÷åíèå äåëèòåëÿ ÷àñòîòû ãåíåðàöèè äî #X502_OUT_FREQ_DIV_MIN_REF_LF */ +#define X502_OUT_FREQ_REF_LF_VAL 700000 + + +/** Ìàêñèìàëüíîå çíà÷åíèå ìåæêàäðîâîé çàäåðæêè äëÿ ÀÖÏ */ +#define X502_ADC_INTERFRAME_DELAY_MAX (0x1FFFFF) +/** Ìàêñèìàëüíîå çíà÷åíèå ìåæêàäðîâîé çàäåðæêè äëÿ ÀÖÏ E16*/ +#define E16_ADC_INTERFRAME_DELAY_MAX (0xFFFF) + +/** Òàéìàóò ïî óìîë÷àíèþ äëÿ âûïîëíåíèÿ êîìàíäû ê BlackFin*/ +#define X502_BF_CMD_DEFAULT_TOUT 500 + +/** Êîä ÀÖÏ, ñîîòâåòñòâóþùèé ìàêñèìàëüíîìó çíà÷åíèþ øêàëû */ +#define X502_ADC_SCALE_CODE_MAX 6000000 +/** Êîä ÀÖÏ, ñîîòâåòñòâóþùèé ìàêñèìàëüíîìó çíà÷åíèþ øêàëû */ +#define E16_ADC_SCALE_CODE_MAX (0x80 * 0x7fff) +/** Êîä ÀÖÏ, ñîîòâåòñòâóþùèé ìàêñèìàëüíîìó çíà÷åíèþ øêàëû */ +#ifndef LTA11_ADC_SCALE_CODE_MAX + #define LTA11_ADC_SCALE_CODE_MAX (0x80 * 0x7fff) +#endif +/** Êîä ÖÀÏ, ñîîòâåòñòâóþùèé ìàêñèìàëüíîìó çíà÷åíèþ øêàëû */ +#define X502_DAC_SCALE_CODE_MAX 30000 +#define E16_DAC_SCALE_CODE_MAX 0x7fff +#define LTA37_DAC_SCALE_CODE_MAX 0x650000 + +/** Ìàêñèìàëüíîå êîëè÷åñòâî ñèìâîëîâ â ñòðîêå ñ íàçâàíèåì óñòðîéñòâà */ +#define X502_DEVNAME_SIZE 32 +/** Ìàêñèìàëüíîå êîëè÷åñòâî ñèìâîëîâ â ñòðîêå ñ ñåðèéíûì íîìåðîì */ +#define X502_SERIAL_SIZE 32 +/** Ìàêñèìàëüíîå êîëè÷åñòâî ñèìâîëîâ â ñòðîêå ñ èäåíòèôèêàòîðîì board implementation */ +#define X502_IMPLEMENTATION_SIZE 16 +/** Ìàêñèìàëüíîå êîëè÷åñòâî ñèìâîëîâ â ñòðîêå ñ îïèñàíèåì ïîäêëþ÷åíèÿ */ +#define X502_LOCATION_STR_SIZE 64 +/** Ðàçìåð MAC-àäðåñà äëÿ Ethernet èíòåðôåéñà */ +#define X502_MAC_ADDR_SIZE 6 +/** Ðàçìåð ñòðîêè ñ îïèñàíèåì ýêçåìïëÿðà óñòðîéñòâà */ +#define X502_INSTANCE_NAME_SIZE 64 +/** Ìàêñèìàëüíûé ðàçìåð ñòðîêè ñ ïàðîëåì íà íàñòðîéêè */ +#define X502_PASSWORD_SIZE 32 + +/** Ìàêñèìàëüíî âîçìîæíîå çíà÷åíèå âíåøíåé îïîðíîé ÷àñòîòû */ +#define X502_EXT_REF_FREQ_MAX 1500000 + +/** Ìàêñèìàëüíî âîçìîæíîå çíà÷åíèå âíåøíåé îïîðíîé ÷àñòîòû äëÿ E502-P1 */ +#define E502_P1_EXT_REF_FREQ_MAX 2000000 + + +/** Ðàçìåð ïîëüçîâàòåëüñêîé îáëàñòè Flash-ïàìÿòè */ +#define X502_FLASH_USER_SIZE 0x100000 + +/** Ñòàíäàðòíûé òàéìàóò íà âûïîëíåíèå çàïðîñà ê BlackFin â ìñ */ +#define X502_BF_REQ_TOUT 500 + + +/** Äèàïàçîí ÖÀÏ â âîëüòàõ */ +#define X502_DAC_RANGE 5. +#define E16_DAC_RANGE 10. + +/** Âûõîä 1:1 è 1:10 */ +#define LTA37_DAC_RANGE20V 20. +#define LTA37_DAC_RANGE2V 2. + +#define LTA37_DACRANGE_CNT 2 + +/** Êîëè÷åñòâî êàíàëîâ ÖÀÏ */ +#define X502_DAC_CH_CNT 2 +#ifndef LTA37_DAC_CH_CNT + #define LTA37_DAC_CH_CNT 10 +#endif + + +/** Ìàêñèìàëüíîå êîëè÷åñòâî êàíàëîâ ÖÀÏ ïîääåðæèâàåìîå áèáëèîòåêîé*/ +#define X502_DAC_CH_CNT_MAX 20 + +/** Êîëè÷åñòâî öèôðîâûõ âûõîäîâ ó ìîäóëÿ */ +#define X502_DOUT_LINES_CNT 16 + + +/** ñëîâî â ïîòîêå, îçíà÷àþùåå, ÷òî ïðîèçîøëî ïåðåïîëíåíèå */ +#define X502_STREAM_IN_MSG_OVERFLOW 0x01010000 + + +/** Çíà÷åíèå ïîëÿ ñèãíàòóðû â çàïèñè î óñòðîéñòâå #t_x502_devrec. Ïðèçíàê, + ÷òî çàïèñü äåéñòâèòåëüíà (óñòàíàâëèâàåòñÿ ôóíêöèÿìè ïî ïîëó÷åíèþ çàïèñåé + î óñòðîéñòâàõ) */ +#define X502_DEVREC_SIGN 0x4C524543 + + +/** Êîäû îøèáîê áèáëèîòåêè */ +typedef enum { + /** Ôóíêöèÿ âûïîëíåíà áåç îøèáîê */ + X502_ERR_OK = 0, + /**  ôóíêöèþ ïåðåäàí íåäåéñòâèòåëüíûé îïèñàòåëü ìîäóëÿ */ + X502_ERR_INVALID_HANDLE = -1, + /** Îøèáêà âûäåëåíèÿ ïàìÿòè */ + X502_ERR_MEMORY_ALLOC = -2, + /** Ïîïûòêà îòêðûòü óæå îòêðûòîå óñòðîéñòâî */ + X502_ERR_ALREADY_OPENED = -3, + /** Óñòðîéñòâî ñ çàäàííûìè ïàðàìåòðàìè íå íàéäåíî â ñèñòåìå */ + X502_ERR_DEVICE_NOT_FOUND = -4, + /** Äîñòóï ê óñòðîéñòâó çàïðåùåí (Êàê ïðàâèëî èç-çà òîãî, ÷òî óñòðîéñòâî + óæå îòêðûòî â äðóãîé ïðîãðàììå) */ + X502_ERR_DEVICE_ACCESS_DENIED = -5, + /** Îøèáêà îòêðûòèÿ óñòðîéñòâà */ + X502_ERR_DEVICE_OPEN = -6, + /**  ôóíêöèþ ïåðåäàí íåäåéñòâèòåëüíûé óêàçàòåëü */ + X502_ERR_INVALID_POINTER = -7, + /** Ôóíêöèÿ íå ìîæåò áûòü âûïîëíåíà ïðè çàïóùåííîì ïîòîêå ñáîðà äàííûõ */ + X502_ERR_STREAM_IS_RUNNING = -8, + /** Îøèáêà ÷òåíèÿ äàííûõ ñèíõðîííîãî ââîäà, äëÿ tcp-ñîåäèíåíèÿ òèï îøèáêè ñîäåðæèòñÿ â errno */ + X502_ERR_RECV = -9, + /** Îøèáêà çàïèñè äàííûõ äëÿ ñèíõðîííîãî âûâîäà */ + X502_ERR_SEND = -10, + /** Ïðîèçîøëî ïåðåïîëíåíèå âíóòðåííåãî áóôåðà äëÿ ïîòîêà ñèíõðîííîãî ââîäà */ + X502_ERR_STREAM_OVERFLOW = -11, + /** Íåèçâåñòíîå ñîîáùåíèå â ïîòîêå ñèíõðîííîãî ââîäà */ + X502_ERR_UNSUP_STREAM_MSG = -12, + /** Îøèáêà ñîçäàíèÿ ñèñòåìíîãî ìüþòåêñà */ + X502_ERR_MUTEX_CREATE = -13, + /** Íåâåðíûé îïèñàòåëü ìüþòåêñà */ + X502_ERR_MUTEX_INVALID_HANDLE = -14, + /** Èñòåêëî âðåìÿ îæèäàíèÿ îñâîáîæäåíèÿ ìüþòåêñà */ + X502_ERR_MUTEX_LOCK_TOUT = -15, + /** Îøèáêà îñâîáîæäåíèÿ ìüþòåêñà */ + X502_ERR_MUTEX_RELEASE = -16, + /** Íåäîñòàòî÷íî ñèñòåìíûõ ðåñóðñîâ */ + X502_ERR_INSUFFICIENT_SYSTEM_RESOURCES= -17, + /** Äàííàÿ âîçìîæíîñòü åùå íå ðåàëèçîâàíà */ + X502_ERR_NOT_IMPLEMENTED = -18, + /** Íåäîñòàòî÷íûé ðàçìåð ìàññèâà */ + X502_ERR_INSUFFICIENT_ARRAY_SIZE = -19, + /** Îøèáêà ÷òåíèÿ ðåãèñòðà FPGA */ + X502_ERR_FPGA_REG_READ = -20, + /** Îøèáêà çàïèñè ðåãèñòðà FPGA */ + X502_ERR_FPGA_REG_WRITE = -21, + /** Ñáîð äàííûõ óæå îñòàíîâëåí */ + X502_ERR_STREAM_IS_NOT_RUNNING = -22, + /** Îøèáêà îñâîáîæäåíèÿ èíòåðôåéñà */ + X502_ERR_INTERFACE_RELEASE = -23, + /** Îøèáêà çàïóñêà ïîòîêà */ + X502_ERR_THREAD_START = -24, + /** Îøèáêà îñòàíîâà ïîòîêà */ + X502_ERR_THREAD_STOP = -25, + /** Óñòðîéñòâî áûëî îòêëþ÷åíî */ + X502_ERR_DEVICE_DISCONNECTED = -26, + /** Íåâåðíûé ðàçìåð îòâåòà íà óïðàâëÿþùèé çàïðîñ */ + X502_ERR_IOCTL_INVALID_RESP_SIZE = -27, + /** Íåâåðíûé òèï óñòðîéñòâà */ + X502_ERR_INVALID_DEVICE = -28, + /** Íåäåéñòâèòåëüíàÿ çàïèñü î óñòðîéñòâå */ + X502_ERR_INVALID_DEVICE_RECORD = -29, + /** Íåâåðíûé îïèñàòåëü êîíôèãóðàöèè ìîäóëÿ */ + X502_ERR_INVALID_CONFIG_HANDLE = -30, + /** Ñâÿçü ñ óñòðîéñòâîì çàêðûòà èëè íå áûëà óñòàíîâëåíà */ + X502_ERR_DEVICE_NOT_OPENED = -31, + /** Äàííàÿ îïåðàöèÿ íå äîñòóïíà äëÿ òåêóùåãî èíòåðôåéñà ñâÿçè ñ óñòðîéñòâîì */ + X502_ERR_INVALID_OP_FOR_IFACE = -32, + /** Íå çàãðóæåí ÏËÈÑ ìîäóëÿ */ + X502_ERR_FPGA_NOT_LOADED = -33, + /** Íåâåðíàÿ êîíôèãóðàöèÿ USB-óñòðîéñòâà */ + X502_ERR_INVALID_USB_CONFIGURATION = -34, + /** Íåâåðíûé îïèñàòåëü êîíòåêñòà ïîèñêà óñòðîéñòâ â ñåòè */ + X502_ERR_INVALID_SVC_BROWSE_HANDLE = -35, + /** Íåâåðíûé îïèñàòåëü çàïèñè î ñåðâèñå */ + X502_ERR_INVALID_SVC_RECORD_HANDLE = -36, + /** Íå çàïóùåíà ïðîãðàììà îáíàðóæåíèÿ óñòðîéñòâ â ëîêàëüíîé ñåòè */ + X502_ERR_DNSSD_NOT_RUNNING = -37, + /** Îøèáêà ïðè îáðàùåíèè ê ïðîãðàììå îáíàðóæåíèÿ óñòðîéñòâ â ëîêàëüíîé ñåòè */ + X502_ERR_DNSSD_COMMUNICATION = -38, + /** Ïðåâûøåí òàéìàóò çàïðîñà ïàðàìåòðîâ àâòîîáíàðóæåííîãî ñåòåâîãî óñòðîéñòâà */ + X502_ERR_SVC_RESOLVE_TIMEOUT = -39, + /** Îøèáêà â êîäèðîâêå èìåíè ýêçåìïëÿðà óñòðîéñòâà */ + X502_ERR_INSTANCE_NAME_ENCODING = -40, + /** Ýêçåìïëÿðû ìîäóëåé íå ñîâïàäàþò */ + X502_ERR_INSTANCE_MISMATCH = -41, + /** Âîçìîæíîñòü íå ïîääåðæèâàåòñÿ òåêóùåé âåðñèåé ïðîøèâêè óñòðîéñòâà */ + X502_ERR_NOT_SUP_BY_FIRMWARE = -42, + /** Âîçìîæíîñòü íå ïîääåðæèâàåòñÿ òåêóùåé âåðñèåé äðàéâåðà óñòðîéñòâà */ + X502_ERR_NOT_SUP_BY_DRIVER = -43, + /** Ïðåâûøåíî âðåìÿ îæèäàíèÿ óñòàíîâëåíèÿ öèêëè÷åñêîãî ñèãíàëà íà âûâîä */ + X502_ERR_OUT_CYCLE_SETUP_TOUT = -44, + /** Íåèçâåñòíûé êîä ïîääåðæèâàåìîé âîçìîæíîñòè */ + X502_ERR_UNKNOWN_FEATURE_CODE = -45, + + + /** Çàäàí íåâåðíûé ðàçìåð ëîãè÷åñêîé òàáëèöû */ + X502_ERR_INVALID_LTABLE_SIZE = -102, + /** Çàäàí íåâåðíûé íîìåð ëîãè÷åñêîãî êàíàëà */ + X502_ERR_INVALID_LCH_NUMBER = -103, + /** Íåâåðíî çàäàíî çíà÷åíèå äèàïàçîíà ÀÖÏ */ + X502_ERR_INVALID_LCH_RANGE = -104, + /** Íåâåðíî çàäàí ðåæèì èçìåðåíèÿ äëÿ ëîãè÷åñêîãî êàíàëà */ + X502_ERR_INVALID_LCH_MODE = -105, + /** Íåâåðíî çàäàí íîìåð ôèçè÷åñêîãî êàíàëà ïðè íàñòðîéêå ëîãè÷åñêîãî */ + X502_ERR_INVALID_LCH_PHY_NUMBER = -106, + /** Íåâåðíî çàäàí ðàçìåð óñðåäíåíèÿ äëÿ ëîãè÷åñêîãî êàíàëà */ + X502_ERR_INVALID_LCH_AVG_SIZE = -107, + /** Íåâåðíî çàäàí äåëèòåëü ÷àñòîòû ñáîðà äàííûõ ÀÖÏ */ + X502_ERR_INVALID_ADC_FREQ_DIV = -108, + /** Íåâåðíî çàäàí äåëèòåëü ÷àñòîòû ñèíõðîííîãî ââîäà öèôðîâûõ ëèíèé */ + X502_ERR_INVALID_DIN_FREQ_DIV = -109, + /** Íåâåðíî çàäàí ðåæèì ðàáîòû ìîäóëÿ */ + X502_ERR_INVALID_MODE = -110, + /** Íåâåðíûé íîìåð êàíàëà ÖÀÏ */ + X502_ERR_INVALID_DAC_CHANNEL = -111, + /** Íåâåðíûé êîä âûáîðà îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè */ + X502_ERR_INVALID_REF_FREQ = -112, + /** Íåâåðíî çàäàíî çíà÷åíèå ìåæêàäðîâîé çàäåðæêè */ + X502_ERR_INVALID_INTERFRAME_DELAY = -113, + /** Íåâåðíî çàäàí ðåæèì ñèíõðîíèçàöèè */ + X502_ERR_INVALID_SYNC_MODE = -114, + /** Íåâåðíî çàäàí íîìåð ïîòîêà äàííûõ */ + X502_ERR_INVALID_STREAM_CH = -115, + /** Íåâåðíî çàäàí äåëèòåëü ÷àñòîòû ñèíõðîííîãî âûâîäà */ + X502_ERR_INVALID_OUT_FREQ_DIV = -116, + + /** Îøèáêà çàõâàòà îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè */ + X502_ERR_REF_FREQ_NOT_LOCKED = -131, + /** Óïðàâëÿþùèé çàïðîñ ê äðàéâåðó çàâåðøåí ñ îøèáêîé */ + X502_ERR_IOCTL_FAILD = -132, + /** Èñòåê òàéìàóò îæèäàíèÿ çàâåðøåíèÿ âûïîëíåíèÿ óïðàâëÿþùåãî çàïðîñà ê äðàéâåðó */ + X502_ERR_IOCTL_TIMEOUT = -133, + /** Îøèáêà ïîëó÷åíèÿ èíôîðìàöèè î óñòðîéñòâå îò äðàéâåðà */ + X502_ERR_GET_INFO = -134, + /** Çà âðåìÿ îæèäàíèÿ íå áûëî ñ÷èòàíî íîâîå ñëîâî ñ öèôðîâûõ ëèíèé */ + X502_ERR_DIG_IN_NOT_RDY = -135, + /** Ïðèíÿòî íåäîñòàòî÷íî ñëîâ îò ìîäóëÿ */ + X502_ERR_RECV_INSUFFICIENT_WORDS = -136, + /** Ïîïûòêà âûïîëíèòü îïåðàöèþ, òðåáóþùóþ íàëè÷èå ÖÀÏ, ïðè åãî îòñóòñòâèè */ + X502_ERR_DAC_NOT_PRESENT = -137, + /** Ïåðåäàíî íåäîñòàòî÷íî ñëîâ â ìîäóëü */ + X502_ERR_SEND_INSUFFICIENT_WORDS = -138, + /** Íå ïðèøëî îòâåòà íà ïåðåäàííóþ êîìàíäó */ + X502_ERR_NO_CMD_RESPONSE = -139, + + /** Íåâåðíûé íîìåð êàíàëà â îáðàáàòûâàåìîì ïîòîêå ñèíõðîííîãî ââîäà */ + X502_ERR_PROC_INVALID_CH_NUM = -140, + /** Íåâåðíûé êîä äèàïàçîíà â îáðàáàòûâàåìîì ïîòîêå ñèíõðîííîãî ââîäà */ + X502_ERR_PROC_INVALID_CH_RANGE = -141, + /** Çàäàí íåâåðíûé àäðåñ âî Flash-ïàìÿòè */ + X502_ERR_FLASH_INVALID_ADDR = -142, + /** Çàäàí íåâåðíûé ðàçìåð áëîêà äàííûõ ïðè ðàáîòå ñ Flash-ïàìÿòüþ */ + X502_ERR_FLASH_INVALID_SIZE = -143, + /** Èñòåê òàéìàóò îæèäàíèÿ çàâåðøåíèÿ çàïèñè âî Flash-ïàìÿòü */ + X502_ERR_FLASH_WRITE_TOUT = -144, + /** Èñòåê òàéìàóò îæèäàíèÿ çàâåðøåíèÿ ñòèðàíèÿ áëîêà Flash-ïàìÿòè */ + X502_ERR_FLASH_ERASE_TOUT = -145, + /** Çàäàííàÿ îáëàñòü äëÿ ñòèðàíèÿ Flash-ïàìÿòè íàðóøàåò ãðàíèöó áëîêà â 4 Êáàéò */ + X502_ERR_FLASH_SECTOR_BOUNDARY = -146, + + /** Íå óäàëîñü îòêðûòü ñîêåò äëÿ ñîåäèíåíèÿ */ + X502_ERR_SOCKET_OPEN = -147, + /** Ïðåâûøåíî âðåìÿ ïîäêëþ÷åíèÿ */ + X502_ERR_CONNECTION_TOUT = -148, + /** Ñîåäèíåíèå çàêðûòî äðóãîé óñòðîéñòâîì */ + X502_ERR_CONNECTION_CLOSED_BY_DEV = -149, + /** Íå óäàëîñü óñòàíîâèòü çàäàííûé ðàçìåð áóôåðà ñîêåòà */ + X502_ERR_SOCKET_SET_BUF_SIZE = -150, + /** Ñîåäèíåíèå äëÿ ïåðåäà÷è äàííûõ íå óñòàíîâëåíî */ + X502_ERR_NO_DATA_CONNECTION = -151, + /** Íå óäàëîñü äîæäàòüñÿ ñîîáùåíèÿ î çàâåðøåíèè ïîòîêà */ + X502_ERR_NO_STREAM_END_MSG = -152, + /** Ñîåäèíåíèå áûëî ñáðîøåíî äðóãîé ñòîðîíîé */ + X502_ERR_CONNECTION_RESET = -153, + /** Íå óäàëîñü íàéòè õîñò ñ óêàçàííûì àäðåñîì */ + X502_ERR_HOST_UNREACHABLE = -154, + /** Îøèáêà óñòàíîâëåíèÿ TCP-ñîåäèíåíèÿ, + ìîæåò âîçíèêàòü êîãäà ñîåäèíåíèå ñ ìîäóëåì óæå óñòàíîâëåíî + (òîëüêî îäíî TCP ñîåäèíåíèå ìîæåò óïðàâëÿòü ìîäóëåì) */ + X502_ERR_TCP_CONNECTION_ERROR = -155, + + + + + /** Íå óäàëîñü îòêðûòü ôàéë ïðîøèâêè BlackFin */ + X502_ERR_LDR_FILE_OPEN = -180, + /** Îøèáêà ÷òåíèÿ èç ôàëà ïðîøèâêè BlackFin */ + X502_ERR_LDR_FILE_READ = -181, + /** Íåâåðíûé ôîðìàò ôàéëà ïðîøèâêè BlackFin */ + X502_ERR_LDR_FILE_FORMAT = -182, + /** Èñïîëüçóþòñÿ âîçìîæíîñòü LDR-ôàéëà, íåäîñòóïíûå ïðè çàïèñè ïðîøèâêè + BlackFin ïî HDMA */ + X502_ERR_LDR_FILE_UNSUP_FEATURE = -183, + /** Íåâåðíûé ñòàðòîâûé àäðåñ ïðîãðàììû â ïðîøèâêå BlackFin */ + X502_ERR_LDR_FILE_UNSUP_STARTUP_ADDR = -184, + /** Èñòåê òàéìàóò âûïîëíåíèÿ çàïðîñà íà ÷òåíèÿ/çàïèñü ïàìÿòè BlackFin */ + X502_ERR_BF_REQ_TIMEOUT = -185, + /** Êîìàíäà äëÿ BlackFin âñå åùå íàõîäèòñÿ â ïðîöåññå îáðàáîòêè */ + X502_ERR_BF_CMD_IN_PROGRESS = -186, + /** Èñòåêëî âðåìÿ âûïîëíåíèÿ óïðàâëÿþùåé êîìàíäû ïðîöåññîðîì BlackFin */ + X502_ERR_BF_CMD_TIMEOUT = -187, + /** Âîçâðàùåíî íåäîñòàòî÷íî äàííûõ â îòâåò íà êîìàíäó ê BlackFin */ + X502_ERR_BF_CMD_RETURN_INSUF_DATA = -188, + /** Èñòåê òàéìàóò îæèäàíèÿ ãîòîâíîñòè ïðîöåññîðà BlackFin ê çàïèñè ïðîøèâêè */ + X502_ERR_BF_LOAD_RDY_TOUT = -189, + /** Ïîïûòêà âûïîëíèòü îïåðàöèþ äëÿ êîòîðîé íóæåí ñèãíàëüíûé ïðîöåññîð ïðè + îòñóòñòâèè ñèãíàëüíîãî ïðîöåññîðà â ìîäóëå */ + X502_ERR_BF_NOT_PRESENT = -190, + /** Íåâåðíûé àäðåñ ïàìÿòè BlackFin ïðè çàïèñè èëè ÷òåíèè ïî HDMA */ + X502_ERR_BF_INVALID_ADDR = -191, + /** Íåâåðíûé ðàçìåð äàííûõ, ïåðåäàâàåìûõ ñ óïðàâëÿþùåé êîìàíäîé â BlackFin */ + X502_ERR_BF_INVALID_CMD_DATA_SIZE = -192 +} t_x502_errs; + + +/** Èíòåðôåéñ ñîåäèíåíèÿ ñ ìîäóëåì */ +typedef enum { + X502_IFACE_UNKNOWN = 0, /**< Íåèçâåñòíûé èíòåðôåéñ */ + X502_IFACE_USB = 1, /**< Óñòðîéñòâî ïîäêëþ÷åíî ïî USB */ + X502_IFACE_ETH = 2, /**< Óñòðîéñòâî ïîäêëþ÷åíî ïî Ethernet ÷åðåç TCP/IP */ + X502_IFACE_PCI = 3 /**< Óñòðîéñòâî ïîäêëþ÷åíî ïî PCI/PCIe */ +} t_x502_iface; + +/** Ôëàãè, óïðàâëÿþùèå ïîèñêîì ïðèñóòñòâóþùèõ ìîäóëåé */ +typedef enum { + /** Ïðèçíàê, ÷òî íóæíî âåðíóòü ñåðèéíûå íîìåðà òîëüêî òåõ óñòðîéñòâ, + êîòîðûå åùå íå îòêðûòû */ + X502_GETDEVS_FLAGS_ONLY_NOT_OPENED = 1 +} t_x502_getdevs_flags; + + + +/** @brief Ôëàãè äëÿ óïðàâëåíèÿ öèôðîâûìè âûõîäàìè + + Ôëàãè óïðàâëåíèÿ öèôðîâûìè âûõîäàìè. Ìîãóò áûòü îáúåäèíåíû ÷åðåç ëîãè÷åñêîå + “ÈËÈ” ñî çíà÷åíèÿìè öèôðîâûõ âûõîäîâ ïðè àñèíõðîííîì âûâîäå ñ ïîìîùüþ + X502_AsyncOutDig() èëè ïåðåäàíû â X502_PrepareData() ïðè ñèíõðîííîì âûâîäå.*/ +typedef enum { + X502_DIGOUT_WORD_DIS_H = 0x00020000, /**< Çàïðåùåíèå (ïåðåâîä â òðåòüå ñîñòîÿíèå) + ñòàðøåé ïîëîâèíû öèôðîâûõ âûõîäîâ */ + X502_DIGOUT_WORD_DIS_L = 0x00010000, /**< Çàïðåùåíèå ìëàäøåé ïîëîâèíû + öèôðîâûõ âûõîäîâ */ + E16_DIGOUT_WORD_DIS = 0x00030000 /**< Äëÿ ìîäóëÿ E16 ïîääåðæèâàåòñÿ òîëüêî ïîëíîå + çàïðåùåíèå öèôðîâûõ âûõîäîâ */ +} t_x502_digout_word_flags; + +/** Êîíñòàíòû äëÿ âûáîðà îïîðíîé ÷àñòîòû */ +typedef enum { + E16_REF_FREQ_48000KHZ = 48000000, /**< ×àñòîòà 48ÌÃö äëÿ E16 */ +/** Îïðåäåëåíèÿ äëÿ LTA, áóäóò ïåðåíåñåíû â lta_api */ +#ifndef LTA11_REF_FREQ_30000KHZ + #define LTA11_REF_FREQ_30000KHZ 30000000 /**< ×àñòîòà 30ÌÃö äëÿ LTA11 */ +#endif +#ifndef LTA37_REF_FREQ_24576KHZ + #define LTA37_REF_FREQ_24576KHZ 24576000 /**< ×àñòîòà 24,576ÌÃö äëÿ LTA37 */ +#endif + X502_REF_FREQ_2000KHZ = 2000000, /**< ×àñòîòà 2ÌÃö */ + X502_REF_FREQ_1500KHZ = 1500000 /**< ×àñòîòà 1.5ÌÃö */ +} t_x502_ref_freq; + + +/** ïîëÿ io_mode â ðåãèñòðå io_hard */ +typedef enum { + X502_MODE_REF_FREQ_2000 = 0, /**< ×àñòîòà 2ÌÃö */ + X502_MODE_REF_FREQ_1500 = 2, /**< ×àñòîòà 1.5ÌÃö */ + E16_MODE_REF_FREQ_48000 = 3 /**< ×àñòîòà 48ÌÃö äëÿ E16*/ +} t_x502_io_mode; + +/** Äèàïàçîíû èçìåðåíèÿ äëÿ êàíàëà ÀÖÏ */ +typedef enum { + X502_ADC_RANGE_10 = 0, /**< Äèàïàçîí +/-10V */ + X502_ADC_RANGE_5 = 1, /**< Äèàïàçîí +/-5V */ + X502_ADC_RANGE_2 = 2, /**< Äèàïàçîí +/-2V */ + X502_ADC_RANGE_1 = 3, /**< Äèàïàçîí +/-1V */ + X502_ADC_RANGE_05 = 4, /**< Äèàïàçîí +/-0.5V */ + X502_ADC_RANGE_02 = 5 /**< Äèàïàçîí +/-0.2V */ +} t_x502_adc_range; + +/** Äèàïàçîíû èçìåðåíèÿ äëÿ êàíàëà ÀÖÏ E16 */ +typedef enum { + E16_ADC_RANGE_10000 = 0, /**< Äèàïàçîí +/-10V */ + E16_ADC_RANGE_2500 = 1, /**< Äèàïàçîí +/-2.5V */ + E16_ADC_RANGE_625 = 2, /**< Äèàïàçîí +/-0.625V */ + E16_ADC_RANGE_156 = 3, /**< Äèàïàçîí +/-0.156V */ +} t_e16_adc_range; + +/** Äèàïàçîíû èçìåðåíèÿ äëÿ êàíàëà ÀÖÏ LTA11 */ +#ifndef LTA37_DAC_RANGE_20000 + #define LTA11_ADC_RANGE_10000 0 /**< Äèàïàçîí +/-10V */ +#endif +#ifndef LTA11_ADC_RANGE_2500 + #define LTA11_ADC_RANGE_2500 1 /**< Äèàïàçîí +/-2.5V */ +#endif +#ifndef LTA11_ADC_RANGE_625 + #define LTA11_ADC_RANGE_625 2 /**< Äèàïàçîí +/-0.625V */ +#endif +#ifndef LTA11_ADC_RANGE_156 + #define LTA11_ADC_RANGE_156 3 /**< Äèàïàçîí +/-0.156V */ +#endif + +typedef enum { + X502_DAC_RANGE_5000 = 0, /**< Âûõîä +/-5V äëÿ x502 */ + E16_DAC_RANGE_10000 = 0, /**< Âûõîä +/-10V äëÿ E16 */ +/** Îïðåäåëåíèÿ äëÿ LTA, áóäóò ïåðåíåñåíû â lta_api */ +#ifndef LTA37_DAC_RANGE_20000 + #define LTA37_DAC_RANGE_20000 0 /**< Âûõîä 1:10 +/-20V äëÿ LTA37 */ +#endif +#ifndef LTA37_DAC_RANGE_2000 + #define LTA37_DAC_RANGE_2000 1 /**< Âûõîä 1:1 +/-2V äëÿ LTA37 */ +#endif +} t_x502devs_dac_range; + +/** Ðåæèì èçìåðåíèÿ äëÿ ëîãè÷åñêîãî êàíàëà */ +typedef enum { + X502_LCH_MODE_COMM = 0, /**< Èçìåðåíèå íàïðÿæåíèÿ îòíîñèòåëüíî îáùåé çåìëè */ + X502_LCH_MODE_DIFF = 1, /**< Äèôôåðåíöèàëüíîå èçìåðåíèå íàïðÿæåíèÿ */ + X502_LCH_MODE_ZERO = 2 /**< Èçìåðåíèå ñîáñòâåííîãî íóëÿ */ +} t_x502_lch_mode; + +/** @brief Ðåæèìû ñèíõðîíèçàöèè + + Ðåæèìû çàäàíèÿ èñòî÷íèêà ÷àñòîòû ñèíõðîíèçàöèè è ïðèçíàêà íà÷àëà + ñèíõðîííîãî ââîäà-âûâîäà */ +typedef enum { + X502_SYNC_INTERNAL = 0, /**< Âíóòðåííèé ñèãíàë */ + X502_SYNC_EXTERNAL_MASTER = 1, /**< Îò âíåøíåãî ìàñòåðà ïî ðàçúåìó ìåæìîäóëüíîé ñèíõðîíèçàöèè (START_IN/CONV_IN äëÿ X502_SetSyncStartMode/X502_SetSyncMode) */ + X502_SYNC_DI_SYN1_RISE = 2, /**< Ïî ôðîíòó ñèãíàëà DI_SYN1 */ + X502_SYNC_DI_SYN1_FALL = 3, /**< Ïî ñïàäó ñèãíàëà DI_SYN1 */ + X502_SYNC_DI_SYN2_RISE = 6, /**< Ïî ôðîíòó ñèãíàëà DI_SYN2 */ + X502_SYNC_DI_SYN2_FALL = 7, /**< Ïî ñïàäó ñèãíàëà DI_SYN2 */ + /** Òîëüêî äëÿ E16! + Âàæíî ïðàâèëüíî óñòàíîâèòü íàïðàâëåíèå TRIG èëè INT íà âõîä + ñíÿòèåì ôëàãà E16_MODE_TRIG_OUTPUT èëè E16_MODE_INT_OUTPUT + */ + E16_SYNC_TRIG_RISE = 2, /**< Ïî ôðîíòó ñèãíàëà TRIG */ + E16_SYNC_TRIG_FALL = 3, /**< Ïî ñïàäó ñèãíàëà TRIG */ + E16_SYNC_INT_RISE = 6, /**< Ïî ôðîíòó ñèãíàëà INT */ + E16_SYNC_INT_FALL = 7, /**< Ïî ñïàäó ñèãíàëà INT */ + /** Àíàëîãîâàÿ ñèíõðîíèçàöèÿ ñòàðòà, òîëüêî äëÿ E16*/ + E16_SYNC_ADC_ABOVE_LEVEL = 8, /**< Âûøå óðîâíÿ */ + E16_SYNC_ADC_BELOW_LEVEL = 9, /**< Íèæå óðîâíÿ */ + E16_SYNC_ADC_EDGE_RISE = 10, /**< Ïî ïåðåõîäó ñèãíàëà ñíèçó ââåðõ */ + E16_SYNC_ADC_EDGE_FALL = 11, /**< Ïî ïåðåõîäó ñèãíàëà ñâåðõó âíèç */ + +/** Îïðåäåëåíèÿ äëÿ LTA, áóäóò ïåðåíåñåíû â lta_api */ +#ifndef LTA37_SYNC_INTERNAL + #define LTA37_SYNC_INTERNAL 0 /**< Âíóòðåííèé ñèãíàë */ +#endif +#ifndef LTA37_SYNC_DI2_RISE + #define LTA37_SYNC_DI2_RISE 1 /**< Ïî ôðîíòó ñèãíàëà DI2 */ +#endif +#ifndef LTA37_SYNC_DI2_FALL + #define LTA37_SYNC_DI2_FALL 2 /**< Ïî ñïàäó ñèãíàëà DI2 */ +#endif + +#ifndef LTA37_DO8_START_OUT + #define LTA37_DO8_START_OUT 4 /**< LTA37: âêëþ÷èòü DO8 íà âûâîä ñèãíàëà ñòàðòà */ +#endif + +#ifndef LTA11_SYNC_INTERNAL + #define LTA11_SYNC_INTERNAL 0 /**< Âíóòðåííèé ñèãíàë */ +#endif +#ifndef LTA11_SYNC_EXT_RISE + #define LTA11_SYNC_EXT_RISE 2 /**< Ïî ôðîíòó ñèãíàëà Sync/Start */ +#endif +#ifndef LTA11_SYNC_EXT_FALL + #define LTA11_SYNC_EXT_FALL 3 /**< Ïî ñïàäó ñèãíàëà Sync/Start */ +#endif +#ifndef LTA11_SYNC_EXT_HIGH + #define LTA11_SYNC_EXT_HIGH 6 /**< Ñòàðò ïî âûñîêîìó óðîâíþ ñèãíàëà Start */ +#endif +#ifndef LTA11_SYNC_EXT_LOW + #define LTA11_SYNC_EXT_LOW 7 /**< Ñòàðò ïî íèçêîìó óðîâíþ ñèãíàëà Start */ +#endif + /** Àíàëîãîâàÿ ñèíõðîíèçàöèÿ ñòàðòà, òîëüêî äëÿ LTA11*/ +#ifndef LTA11_SYNC_ADC_ABOVE_LEVEL + #define LTA11_SYNC_ADC_ABOVE_LEVEL 8 /**< Âûøå óðîâíÿ */ +#endif +#ifndef LTA11_SYNC_ADC_BELOW_LEVEL + #define LTA11_SYNC_ADC_BELOW_LEVEL 9 /**< Íèæå óðîâíÿ */ +#endif +#ifndef LTA11_SYNC_ADC_EDGE_RISE + #define LTA11_SYNC_ADC_EDGE_RISE 10 /**< Ïî ïåðåõîäó ñèãíàëà ñíèçó ââåðõ */ +#endif +#ifndef LTA11_SYNC_ADC_EDGE_FALL + #define LTA11_SYNC_ADC_EDGE_FALL 11 /**< Ïî ïåðåõîäó ñèãíàëà ñâåðõó âíèç */ +#endif +} t_x502_sync_mode; + +/** Ôëàãè, óïðàâëÿþùèå îáðàáîòêîé ïðèíÿòûõ äàííûõ */ +typedef enum { + /** Ïðèçíàê, ÷òî íóæíî ïðåîáðàçîâàòü çíà÷åíèÿ ÀÖÏ â âîëüòû */ + X502_PROC_FLAGS_VOLT = 0x00000001, + /** Ïðèçíàê, ÷òî íå íóæíî ïðîâåðÿòü ñîâïàäåíèå íîìåðîâ êàíàëîâ + â ïðèíÿòûõ äàííûõ ñ êàíàëàìè èç ëîãè÷åñêîé òàáëèöû. + Ìîæåò èñïîëüçîâàòüñÿ ïðè íåñòàíäàðòíîé ïðîøèâêå BlackFin + ïðè ïåðåäà÷å â ÏÊ íå âñåõ äàííûõ. */ + X502_PROC_FLAGS_DONT_CHECK_CH = 0x00010000 +} t_x502_proc_flags; + + +/** Ôëàãè äëÿ îáîçíà÷åíèÿ ñèíõðîííûõ ïîòîêîâ äàííûõ */ +typedef enum { + X502_STREAM_ADC = 0x01, /**< Ïîòîê äàííûõ îò ÀÖÏ */ + X502_STREAM_DIN = 0x02, /**< Ïîòîê äàííûõ ñ öèôðîâûõ âõîäîâ */ + X502_STREAM_DAC1 = 0x10, /**< Ïîòîê äàííûõ ïåðâîãî êàíàëà ÖÀÏ */ + X502_STREAM_DAC2 = 0x20, /**< Ïîòîê äàííûõ âòîðîãî êàíàëà ÖÀÏ */ + X502_STREAM_DOUT = 0x40, /**< Ïîòîê äàííûõ íà öèôðîâûå âûâîäû */ + /** Îáúåäèíåíèå âñåõ ôëàãîâ, îáîçíà÷àþùèõ ïîòîêè äàííûõ íà ââîä */ + X502_STREAM_ALL_IN = X502_STREAM_ADC | X502_STREAM_DIN, + /** Îáúåäèíåíèå âñåõ ôëàãîâ, îáîçíà÷àþùèõ ïîòîêè äàííûõ íà âûâîä */ + X502_STREAM_ALL_OUT = X502_STREAM_DAC1 | X502_STREAM_DAC2 | X502_STREAM_DOUT +} t_x502_streams; + +/** Êîíñòàíòû, îïðåäåëÿþùèå òèï ïåðåäàâàåìîãî îòñ÷åòà èç ÏÊ â ìîäóëü */ +typedef enum { + X502_STREAM_OUT_WORD_TYPE_DOUT = 0x0, /**< Öèôðîâîé âûâîä */ + X502_STREAM_OUT_WORD_TYPE_DAC1 = 0x40000000, /**< Êîä äëÿ 1-ãî êàíàëà ÖÀÏ */ + X502_STREAM_OUT_WORD_TYPE_DAC2 = 0x80000000, /**< Êîä äëÿ 2-ãî êàíàëà ÖÀÏ */ +/** Îïðåäåëåíèÿ äëÿ LTA, áóäóò ïåðåíåñåíû â lta_api */ +#ifndef LTA37_STREAM_OUT_WORD_TYPE_DOUT + #define LTA37_STREAM_OUT_WORD_TYPE_DOUT (12 << 24) /**< Öèôðîâîé âûâîä äëÿ LTA37*/ +#endif +} t_x502_stream_out_wrd_type; + +/** Ðåæèì ðàáîòû ìîäóëÿ */ +typedef enum { + X502_MODE_FPGA = 0, /**< Âñå ïîòîêè äàííûõ ïåðåäàþòñÿ ÷åðåç ÏËÈÑ ìèíóÿ + ñèãíàëüíûé ïðîöåññîð BlackFin */ + X502_MODE_DSP = 1, /**< Âñå ïîòîêè äàííûõ ïåðåäàþòñÿ ÷åðåç ñèãíàëüíûé + ïðîöåññîð, êîòîðûé äîëæåí áûòü çàãðóæåí + ïðîøèâêîé äëÿ îáðàáîòêè ýòèõ ïîòîêîâ */ + X502_MODE_DEBUG = 2 /**< Îòëàäî÷íûé ðåæèì */ +} t_x502_mode; + +#define X502_DAC_CH(ch, range, ch_cnt) ((ch_cnt * range) + ch) +#ifndef LTA37_DAC_CH + #define LTA37_DAC_CH(ch, range) X502_DAC_CH(ch, range, LTA37_DAC_CH_CNT) +#endif + +/** @brief Íîìåðà êàíàëîâ ÖÀÏ + + Íîìåð êàíàëîâ ÖÀÏ äëÿ óêàçàíèÿ â X502_AsyncOutDac() */ +typedef enum { + X502_DAC_CH1 = 0, /**< Ïåðâûé êàíàë ÖÀÏ */ + X502_DAC_CH2 = 1, /**< Âòîðîé êàíàë ÖÀÏ */ + +/** Îïðåäåëåíèÿ äëÿ LTA, áóäóò ïåðåíåñåíû â lta_api */ +#ifndef LTA37_DAC_CH1 + #define LTA37_DAC_CH1 0 /**< âûõîä 1:1 1 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH2 + #define LTA37_DAC_CH2 1 /**< âûõîä 1:1 2 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH3 + #define LTA37_DAC_CH3 2 /**< âûõîä 1:1 3 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH4 + #define LTA37_DAC_CH4 3 /**< âûõîä 1:1 4 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH5 + #define LTA37_DAC_CH5 4 /**< âûõîä 1:1 5 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH6 + #define LTA37_DAC_CH6 5 /**< âûõîä 1:1 6 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH7 + #define LTA37_DAC_CH7 6 /**< âûõîä 1:1 7 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH8 + #define LTA37_DAC_CH8 7 /**< âûõîä 1:1 8 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH9 + #define LTA37_DAC_CH9 8 /**< âûõîä 1:1 9 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH10 + #define LTA37_DAC_CH10 9 /**< âûõîä 1:1 10 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH1_10 + #define LTA37_DAC_CH1_10 LTA37_DAC_CH(LTA37_DAC_CH1, LTA37_DAC_RANGE_2000) /**< âûõîä 1:10 1 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH2_10 + #define LTA37_DAC_CH2_10 LTA37_DAC_CH(LTA37_DAC_CH1, LTA37_DAC_RANGE_2000) /**< âûõîä 1:10 2 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH3_10 + #define LTA37_DAC_CH3_10 LTA37_DAC_CH(LTA37_DAC_CH1, LTA37_DAC_RANGE_2000) /**< âûõîä 1:10 3 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH4_10 + #define LTA37_DAC_CH4_10 LTA37_DAC_CH(LTA37_DAC_CH1, LTA37_DAC_RANGE_2000) /**< âûõîä 1:10 4 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH5_10 + #define LTA37_DAC_CH5_10 LTA37_DAC_CH(LTA37_DAC_CH1, LTA37_DAC_RANGE_2000) /**< âûõîä 1:10 5 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH6_10 + #define LTA37_DAC_CH6_10 LTA37_DAC_CH(LTA37_DAC_CH1, LTA37_DAC_RANGE_2000) /**< âûõîä 1:10 6 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH7_10 + #define LTA37_DAC_CH7_10 LTA37_DAC_CH(LTA37_DAC_CH1, LTA37_DAC_RANGE_2000) /**< âûõîä 1:10 7 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH8_10 + #define LTA37_DAC_CH8_10 LTA37_DAC_CH(LTA37_DAC_CH1, LTA37_DAC_RANGE_2000) /**< âûõîä 1:10 8 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH9_10 + #define LTA37_DAC_CH9_10 LTA37_DAC_CH(LTA37_DAC_CH1, LTA37_DAC_RANGE_2000) /**< âûõîä 1:10 9 êàíàëà ÖÀÏ LTA37 */ +#endif +#ifndef LTA37_DAC_CH10_10 + #define LTA37_DAC_CH10_10 LTA37_DAC_CH(LTA37_DAC_CH1, LTA37_DAC_RANGE_2000) /**< âûõîä 1:10 10 êàíàëà ÖÀÏ LTA37 */ +#endif +} t_x502_dac_ch; + +/** @brief Ôëàãè, èñïîëüçóåìûå ïðè âûâîäå äàííûõ íà ÖÀÏ + + Ôëàãè, êîìáèíàöèþ êîòîðûõ ìîæíî ïåðåäàòü â X502_AsyncOutDac() èëè + X502_PrepareData(), ÷òîáû îïðåäåëèòü äåéñòâèÿ, êîòîðûå äîëæíû âûïîëíèòü + ýòè ôóíêöèè ñ ïåðåäàííûì çíà÷åíèåì ïåðåä âûâîäîì èõ íà ÖÀÏ */ +typedef enum { + /** Óêàçûâàåò, ÷òî çíà÷åíèå çàäàíî â Âîëüòàõ è ïðè âûâîäå åãî íóæíî + ïåðåâåñòè åãî â êîäû ÖÀÏ. Åñëè ôëàã íå óêàçàí, òî ñ÷èòàåòñÿ, ÷òî çíà÷åíèå + èçíà÷àëüíî â êîäàõ */ + X502_DAC_FLAGS_VOLT = 0x0001, + /** Óêàçûâàåò, ÷òî íóæíî ïðèìåíèòü êàëèáðîâî÷íûå êîýôôèöèåíòû ïåðåä + âûâîäîì çíà÷åíèÿ íà ÖÀÏ. */ + X502_DAC_FLAGS_CALIBR = 0x0002 +} t_x502_dacout_flags; + + + +/** Íîìåðà êàíàëîâ äëÿ ïåðåäà÷è ïîòîêîâ äàííûõ */ +typedef enum { + X502_STREAM_CH_IN = 0, /**< Îáùèé êàíàë íà ââîä */ + X502_STREAM_CH_OUT = 1, /**< Îáùèé êàíàë íà âûâîä */ + X502_STREAM_CH_CNT +} t_x502_stream_ch; + + +/** @brief Öèôðîâûå ëèíèè, íà êîòîðûõ ìîæíî âêëþ÷èòü ïîäòÿãèâàþùèå ðåçèñòîðû + + Ôëàãè, îïðåäåëÿþùèå íà êàêèõ öèôðîâûõ âõîäàõ äîëæíû áûòü âêëþ÷åíû + ïîäòÿãèâàþùèå ðåçèñòîðû. Äëÿ ðàçíûõ ìîäóëåé äîñòóïíû ðàçíûå íàáîðû ôëàãîâ. */ +typedef enum { + X502_PULLUPS_DI_H = 0x01, /**< Ñòàðøàÿ ïîëîâèíà öèôðîâûõ âõîäîâ (òîëüêî äëÿ L502) */ + X502_PULLUPS_DI_L = 0x02, /**< Ìëàäøàÿ ïîëîâèíà öèôðîâûõ âõîäîâ (òîëüêî äëÿ L502) */ + X502_PULLUPS_DI_SYN1 = 0x04, /**< Ëèíèÿ SYN1 */ + X502_PULLUPS_DI_SYN2 = 0x08, /**< Ëèíèÿ SYN2 */ + X502_PULLDOWN_CONV_IN = 0x10, /**< Ïîäòÿæêà ê 0 ëèíèè ìåæìîäóëüíîé + ñèíõðîíèçàöèè CONV_IN (òîëüêî äëÿ E502) */ + X502_PULLDOWN_START_IN = 0x20, /**< Ïîäòÿæêà ê 0 ëèíèè ìåæìîäóëüíîé + ñèíõðîíèçàöèè START_IN (òîëüêî äëÿ E502) */ + + E16_MODE_TRIG_OUTPUT = 0x04, /**< E16: Ïåðåêëþ÷åíèå TRIG íà âûõîä, èíà÷å âõîä*/ + E16_MODE_INT_OUTPUT = 0x08, /**< E16: Ïåðåêëþ÷åíèå INT íà âûõîä, èíà÷å âõîä*/ + E16_MODE_TRIG_START_OUTPUT = 0x10, /**< E16: Ñèãíàë START íà âûõîäå TRIG, èíà÷å CONV (TRIG äîëæåí áûòü âêëþ÷åí íà âûõîä) */ + E16_MODE_INT_START_OUTPUT = 0x20, /**< E16: Ñèãíàë START íà âûõîäå INT, èíà÷å CONV (INT äîëæåí áûòü âêëþ÷åí íà âûõîä) */ + E16_MODE_RELAY_ON = 0x40, /**< E16: Âêëþ÷èòü ðåëå, âî âêëþ÷åííîì ñîñòîÿíèè íà êîíòàêòàõ ðàçúåìà digital: +15Â, -15Â, âî âûêëþ÷åííîì: +3Â, GND, âòîðè÷íîå ïèòàíèå äîëæíî áûòü âêëþ÷åííûì */ + E16_MODE_RELAY_OFF = 0x80, /**< E16: Âûêëþ÷èòü ðåëå */ + + E16_MODE_PWM_MANUAL = 0x100, /**< E16: Ðó÷íîå óïðàâëåíèå âòîðè÷íûì ïèòàíèåì, + ïî óìîë÷àíèþ âòîðè÷íîå ïèòàíèå âêëþ÷àåòñÿ ïðè ïåðâîì îáðàùåíèè ê óñòðîéñòâó èç API è âûêëþ÷àåòñÿ åñëè áûëî çàêðûòî ñîåäèíåíèå ïî Ethernet èëè îòêëþ÷åíèå USB êàáåëÿ */ + E16_MODE_PWM_ON = 0x200, /**< E16: Âêëþ÷åíèå âòîðè÷íîãî ïèòàíèÿ. Åñëè âêëþ÷åí ôëàã E16_MODE_PWM_MANUAL, òî ñîñòîÿíèå âòîðè÷íîãî ïèòàíèÿ áóäåò çàâèñåòü îò ñîñòîÿíèÿ ýòîãî ôëàãà, èíà÷å ðàáîòà ïî óìîë÷àíèþ */ + +/** Îïðåäåëåíèÿ äëÿ LTA, áóäóò ïåðåíåñåíû â lta_api */ +#ifndef LTA37_RELAY_12_ON + #define LTA37_RELAY_12_ON (1 << 0) /**< LTA37: âêëþ÷èòü ðåëå êàíàëîâ 1, 2 */ +#endif +#ifndef LTA37_RELAY_34_ON + #define LTA37_RELAY_34_ON (1 << 1) /**< LTA37: âêëþ÷èòü ðåëå êàíàëîâ 3, 4 */ +#endif +#ifndef LTA37_RELAY_56_ON + #define LTA37_RELAY_56_ON (1 << 2) /**< LTA37: âêëþ÷èòü ðåëå êàíàëîâ 5, 6 */ +#endif +#ifndef LTA37_RELAY_78_ON + #define LTA37_RELAY_78_ON (1 << 3) /**< LTA37: âêëþ÷èòü ðåëå êàíàëîâ 7, 8 */ +#endif +#ifndef LTA37_RELAY_9A_ON + #define LTA37_RELAY_9A_ON (1 << 4) /**< LTA37: âêëþ÷èòü ðåëå êàíàëîâ 9, 10 */ + +#endif +#ifndef LTA37_RELAY_ALL_CH_ON + #define LTA37_RELAY_ALL_CH_ON (LTA37_RELAY_12_ON | LTA37_RELAY_34_ON | LTA37_RELAY_56_ON | LTA37_RELAY_78_ON | LTA37_RELAY_9A_ON) + /**< LTA37: âêëþ÷åíèå ðåëå âñåõ êàíàëîâ */ +#endif +#ifndef LTA11_MODE_PTP_SYNC + #define LTA11_MODE_PTP_SYNC 0x04 /**< LTA11: ñèíõðîíèçàöèÿ ïî ïðîòîêîëó PTP */ +#endif + +} t_x502_pullups; + + +/** Ôëàãè, îïðåäåëÿþùèå íàëè÷èå îïöèé â ìîäóëå è íàëè÷èå íåîáõîäèìûõ ïàðàìåòðîâ */ +typedef enum { + /** Ïðèçíàê íàëè÷èÿ äâóõêàíàëüíîãî êàíàëüíîãî ÖÀÏ */ + X502_DEVFLAGS_DAC_PRESENT = 0x00000001, + /** Ïðèçíàê íàëè÷èÿ ãàëüâàíîðàçâÿçêè */ + X502_DEVFLAGS_GAL_PRESENT = 0x00000002, + /** Ïðèçíàê íàëè÷èÿ ñèãíàëüíîãî ïðîöåññîðà BlackFin */ + X502_DEVFLAGS_BF_PRESENT = 0x00000004, + /** 3 áèòà, îòâå÷àþùèå çà òèï èñïîëüçóìîãî ïðîöåññîðà/êîíòðîëëåðà (0 --- ADSP-BF523) */ + X502_DEVFLAGS_PROC_TYPE = 0x00000038, + /** 2 áèòà, îòâå÷àþùèå çà òèï èñïîëüçóåìîãî ÖÀÏ (0 --- AD5542AARUZ, 1 --- DAC8581IPW) */ + X502_DEVFLAGS_DAC_TYPE = 0x000000C0, + + /** Ïðèçíàê, ÷òî óñòðîéñòâî ïîääåðæèâàåò èíòåðôåéñ USB */ + X502_DEVFLAGS_IFACE_SUPPORT_USB = 0x00000100, + /** Ïðèçíàê, ÷òî óñòðîéñòâî ïîääåðæèâàåò Ethernet */ + X502_DEVFLAGS_IFACE_SUPPORT_ETH = 0x00000200, + /** Ïðèçíàê, ÷òî óñòðîéñòâî ïîääåðæèâàåò èíòåðôåéñ PCI/PCI-Express */ + X502_DEVFLAGS_IFACE_SUPPORT_PCI = 0x00000400, + + /** Ïðèçíàê, ÷òî óñòðîéñòâî âûïîëíåíî â èíäóñòðèàëíîì èñïîëíåíèè */ + X502_DEVFLAGS_INDUSTRIAL = 0x00008000, + + /** Ïðèçíàê, ÷òî âî Flash-ïàìÿòè ïðèñóòñòâóåò èíôîðìàöèÿ î ìîäóëå */ + X502_DEVFLAGS_FLASH_DATA_VALID = 0x00010000, + /** Ïðèçíàê, ÷òî âî Flash-ïàìÿòè ïðèñóòñòâóþò äåéñòâèòåëüíûå êàëèáðîâî÷íûå + êîýôôèöèåíòû ÀÖÏ */ + X502_DEVFLAGS_FLASH_ADC_CALIBR_VALID = 0x00020000, + /** Ïðèçíàê, ÷òî âî Flash-ïàìÿòè ïðèñóòñòâóþò äåéñòâèòåëüíûå êàëèáðîâî÷íûå + êîýôôèöèåíòû ÖÀÏ */ + X502_DEVFLAGS_FLASH_DAC_CALIBR_VALID = 0x00040000, + X502_DEVFLAGS_FLASH_AFC_CALIBR_VALID = 0x00080000, + + /** Ïðèçíàê, ÷òî ïðèñóòñòâóåò ïðîøèâêà ÏËÈÑ è îíà óñïåøíî áûëà çàãðóæåíà */ + X502_DEVFLAGS_FPGA_LOADED = 0x00800000, + /** Ïðèçíàê, ÷òî óñòðîéñòâî óæå îòêðûòî (äåéñòâèòåëåí òîëüêî âíóòðè t_x502_devrec) */ + X502_DEVFLAGS_DEVREC_OPENED = 0x01000000, + /** Ïðèçíàê, ÷òî ïðèñóòñòâóåò ïðîøèâêà ÏËÈÑ è îíà óñïåøíî áûëà çàãðóæåíà îæèäàåì GD32 äëÿ òîãî ÷òîáû îñòàëüíûå ðåãèñòðû ðàáîòàëè */ + X502_DEVFLAGS_FPGA_AWAITING_GD32 = 0x02000000, + /** Ïðèçíàê, ÷òî ïðèñóòñòâóåò GD32 è ýòî E502-P1 */ + X502_DEVFLAGS_GD_PRESENT = 0x02000000 +} t_x502_dev_flags; + +/** @brief Òèï ñîäåðæèìîãî ñòðîêè ñ ðàñïîëîæåíèåì óñòðîéñòâà + + Äàííîå ïîëå îïðåäåëÿåò ñîäåðæèìîå ïîëÿ location â ñòðóêòóðå #t_x502_devrec */ +typedef enum { + /**  ïîëå ðàñïîëîæåíèÿ óñòðîéñòâà íå ñîäåðæèòñÿ èíôîðìàöèè */ + X502_LOCATION_TYPE_NONE = 0, + /**  ïîëå ðàñïîëîæåíèÿ óñòðîéñòâà ñîäåðæèòñÿ ñòðîêà ñ àäðåñîì óñòðîéñòâà */ + X502_LOCATION_TYPE_ADDR = 1, + /**  ïîëå ðàñïîëîæåíèÿ óñòðîéñòâà ñîäåðæèòñÿ ñòðîêà ñ èìåíåì ýêçåìïëÿðà */ + X502_LOCATION_TYPE_INSTANCE_NAME = 2 +} t_x502_location_type; + + +/** @brief Ôëàãè äëÿ ðåæèìà öèêëè÷åñêîãî âûâîäà + + Äàííûå ôëàãè ìîãóò áûòü ïåðåäàíû â X502_OutCycleSetup() è X502_OutCycleStop() */ +typedef enum { + /** Ôëàã óêàçûâàåò, ÷òî îñòàíîâ èëè ñìåíà ñèãíàëà ìîãóò ïðîèçîéòè áåç îæèäàíèÿ + êîíöà öèêëà ïðåäûäóùåãî ñèãíàëà. Ýòî ïîçâîëÿåò âûïîëíèòü ïåðåêëþ÷åíèå + áûñòðåå (îäíàêî âñå ðàâíî ìîæåò áûòü ïîñòàâëåíî íà ïåðåäà÷ó äî 256 ÊÑåìïëîâ, + êîòîðûå äîëæíû áóäóò áûòü ïåðåäàíû), íî òî÷êà ñìåíû èëè îñòàíîâà + ìîæåò áûòü â ëþáîì ìåñòå ïåðèîäà */ + X502_OUT_CYCLE_FLAGS_FORCE = 0x01, + /** Ôëàã óêàçûâàåò, ÷òî ôóíêöèÿ äîëæíà äîæäàòüñÿ ïîëíîé çàãðóçêè ñèãíàëà + è óñòàíîâêè ñèãíàëà íà âûâîä (äëÿ X502_OutCycleSetup()) èëè çàâåðøåíèÿ + ãåíåðàöèè öèêëè÷åñêîãî ñèãíàëà (äëÿ X502_OutCycleStop()). Áåç íåãî + ôóíêöèè òîëüêî ïîñûëàþò êîìàíäó ìîäóëþ, âîçâðàùàÿ ñðàçó óïðàâëåíèå. + + Äàííîå îæèäàíèå ìîæåò çàíèìàòü çíà÷èòåëüíîå âðåìÿ â çàâèñèìîñòè îò ðàçìåðà ñèãíàëà + (à òàêæå ðàçìåðà ïðåäûäóùåãî ñèãíàëà â ñëó÷àå ñìåíû èëè îñòàíîâà ãåíåðàöèè áåç + #X502_OUT_CYCLE_FLAGS_FORCE). Äàííóþ ïðîâåðêó ìîæíî ñäåëàòü è + îòäåëüíîé ôóíêöèé X502_OutCycleCheckSetupDone(). + Äàííûé ôëàã èìååò çíà÷åíèÿ òîëüêî â òåõ ñëó÷àÿõ, êîãäà ïîääåðæèâàåòñÿ + ôóíêöèÿ X502_OutCycleCheckSetupDone(), â ïðîòèâíîì ñëó÷àå îí èãíîðèðóåòñÿ. */ + X502_OUT_CYCLE_FLAGS_WAIT_DONE = 0x02 +} t_x502_out_cycle_flags; + + + +/** @brief Äîïîëíèòåëüíûå âîçìîæíîñòè ìîäóëÿ + + Êîäû âîçìîæíîñòåé ìîäóëÿ, êîòîðûå ìîãóò ïîääåðæèâàòüñÿ èëè íåò â çàâèñèìîñòè + îò òèïà ìîäóëÿ, âåðñèé ïðîøèâîê è ò.ï. */ +typedef enum { + /** Ïîääåðæêà óñòàíîâêè äåëèòåëÿ ÷àñòîòû âûâîäà, îòëè÷íîãî îò #X502_OUT_FREQ_DIV_DEFAULT */ + X502_FEATURE_OUT_FREQ_DIV = 1, + /** Âîçìîæíîñòü ÷òåíèÿ ôëàãîâ ñîñòîÿíèÿ âûâîäà ñ ïîìîùüþ X502_OutGetStatusFlags() */ + X502_FEATURE_OUT_STATUS_FLAGS = 2 +} t_x502_features; + +/** Ôëàãè ñîñòîÿíèÿ äëÿ ñèíõðîííîãî âûâîäà */ +typedef enum { + /** Ôëàã óêàçûâàåò, ÷òî â íàñòîÿùåå âðåìÿ áóôåð â ìîäóëå íà ïåðåäà÷ó ïóñò */ + X502_OUT_STATUS_FLAG_BUF_IS_EMPTY = 0x01, + /** Ôëàã óêàçûâàåò, ÷òî áûëî îïóñòîøåíèå áóôåðà íà âûâîä ñ íà÷àëà ñòàðòà ñèíõðîííîãî + ââîäà-âûâîäà èëè ñ ìîìåíòà ïîñëåäíåãî ÷òåíèÿ ñòàòóñà ñ ïîìîùüþ + X502_OutGetStatusFlags() (â çàâèñèìîñòè îò òîãî, ÷òî áûëî ïîñëåäíèì) */ + X502_OUT_STATUS_FLAG_BUF_WAS_EMPTY = 0x02 +} t_x502_out_status_flags; + + +/** Ïàðàìåòðû ìîäóëåé */ +typedef enum { + X502_PARAM_UNKNOWN = 0, /**< Íåèçâåñòíûé ïàðàìåòð */ + LTA37_WRITE_CTRL2_REG = 1, /**< çàïèñü êîíòðîëüíîãî ðåãèñòðà äëÿ òåñòîâ */ +} t_x502_params; + +/** @} */ + + +/***************************************************************************//** + @addtogroup type_list Òèïû äàííûõ + @{ + *****************************************************************************/ + +/** @brief Âíóòðåííÿÿ èíôîðìàöèÿ çàïèñè î óñòðîéñòâå + + Íåïðîçðà÷íàÿ ñòðóêòóðà ñ èíôîðìàöèåé, äîñòàòî÷íîé äëÿ óñòàíîâëåíèÿ + ñ íèì ñâÿçè. Çàâèñèò îò òèïà óñòðîéñòâà, èíòåðôåéñà ïîäêëþ÷åíèÿ è íå + äîñòóïíà ïîëüçîâàòåëþ íàïðÿìóþ, à èñïîëüçóåòñÿ áèáëèîòåêîé â + X502_OpenByDevRecord() */ +typedef struct st_x502_devrec_inptr t_x502_devrec_inptr; + +/** @brief Çàïèñü î óñòðîéñòâå + + Ñòðóêòóðà, îïèñûâàþùàÿ óñòðîéñòâî, ïî êîòîðîé ñ íèì ìîæíî óñòàíîâèòü ñîåäèíåíèå */ +typedef struct { + uint32_t sign; /**< Ïðèçíàê äåéñòâèòåëüíîé ñòðóêòóðû. + Åñëè çàïèñü äåéñòâèòåëüíà (ñîîòâåòñòâóåò êàêîìó-ëèáî óñòðîéñòâó), + òî äîëæåí áûòü ðàâåí #X502_DEVREC_SIGN) */ + char devname[X502_DEVNAME_SIZE]; /**< Íàçâàíèå óñòðîéñòâà */ + char serial[X502_SERIAL_SIZE]; /**< Ñåðèéíûé íîìåð */ + char location[X502_LOCATION_STR_SIZE]; /**< Îïèñàíèå ïîäêëþ÷åíèÿ (åñëè åñòü) */ + uint32_t flags; /**< Ôëàãè èç #t_x502_dev_flags, îïèñûâàþùèå óñòðîéñòâî */ + uint8_t iface; /**< Èíòåðôåéñ, ïî êîòîðîìó ïîäêëþ÷åíî óñòðîéñòâî */ + uint8_t location_type; /**< Îïðåäåëÿåò, ÷òî èìåííî ñîõðàíåíî â ïîëå location + (îäíî çíà÷åíèå èç #t_x502_location_type) */ + char res[122]; /**< Ðåçåðâ */ + t_x502_devrec_inptr* internal; /**< Íåïðîçðà÷íûé óêàçàòåëü íà ñòðóêòóðó ñ + äîïîëíèòåëüíîé èíôîðìàöèåé, íåîáõîäèìîé + äëÿ îòêðûòèÿ óñòðîéñòâà */ +} t_x502_devrec; + + +/** @brief Îïèñàòåëü ìîäóëÿ + + Íåïðîçðà÷íûé óêàçàòåëü íà ñòðóêòóðó, + ñîäåðæàùóþ èíôîðìàöèþ î íàñòðîéêàõ ìîäóëÿ è òåêóùåì ñîåäèíåíèè ñ íèì. + Ïîëüçîâàòåëüñêîé ïðîãðàììå íå äîñòóïíû ïîëÿ ñòðóêòóðû íàïðÿìóþ, à òîëüêî + ÷åðåç ôóíêöèè áèáëèîòåêè. + Ôóíêöèè óïðàâëåíèÿ ìîäóëåì ïðèíèìàþò îïèñàòåëü ìîäóëÿ ñâîèì ïåðâûì ïàðàìåòðîì. + Îïèñàòåëü ìîäóëÿ ñîçäàåòñÿ ñ ïîìîùüþ X502_Create() è â êîíöå ðàáîòû + îñâîáîæäàåòñÿ ñ ïîìîùüþ X502_Free(). */ +typedef struct st_x502* t_x502_hnd; + +/** @brief Ñïèñîê ñåðèéíûé íîìåðîâ + + Òèï îïðåäåëÿåò ìàññèâ ñåðèéíûõ íîìåðîâ äëÿ êîëè÷åñòâà ìîäóëåé, îïðåäåëÿåìîãî + íà ýòàïå ðàáîòû ïðîãðàììû. */ +typedef char (*t_x502_serial_list)[X502_SERIAL_SIZE]; + +/** @brief Êàëèáðîâî÷íûå êîýôôèöèåíòû äèàïàçîíà. + + Ñòðóêòóðà ñîäåðæèò êàëèáðîâî÷íûå çíà÷åíèÿ ñìåùåíèÿ íóëÿ è êîýôôèöèåíòà + øêàëû äëÿ îäíîãî äèàïàçîíà ÀÖÏ èëè ÖÀÏ.Ðåçóëüòèðóþùåå çíà÷åíèå ÀÖÏ + âû÷èñëÿåòñÿ êàê (val - offs) * k, ãäå val - íåîòêàëèáðîâàííîå çíà÷åíèå */ +typedef struct { + double offs; /**< ñìåùåíèå íóëÿ */ + double k; /**< êîýôôèöèåíò øêàëû */ +} t_x502_cbr_coef; + +/** @brief Êîýôôèöèåíòû äëÿ êàëèáðîâêè ôèëüðà êîððåêöèè À×Õ + + Ñòðóêòóðà îïèñûâàåò êîýôôèöèåíòû è ïàðàìåòðû, èñïîëüçóåìûå äëÿ êîððåêöèè À×Õ ÖÀÏ */ +typedef struct { + double Fd; /**< ×àñòîòà ãåíåðàöèè ÖÀÏ, íà êîòîðîé èçìåðåíî îòíîøåíèå àìïëèòóä. */ + double SigFreq; /**< ×àñòîòà ñèãíàëà, äëÿ êîòîðîé èçìåðåíî îòíîøåíèå àìïëèòóä. */ + /** Íàáîð îòíîøåíèé ðåàëüíî âûñòàâëåííîé àìïëèòóäû ñèíóñîèäàëüíîãî ñèãíàëà + * ÷àñòîòû @fieldref{TLTR35_AFC_COEF,SigFreq} ê çàäàâàåìîé àìïëèòóäå + * ïðè ÷àñòîòå ãåíåðàöèè @fieldref{TLTR35_AFC_COEF,Fd} äëÿ êàæäîãî êàíàëà ÖÀÏ. */ + double K[X502_DAC_CH_CNT_MAX]; +} t_x502_afc_coef; + +/** @brief Êàëèáðîâî÷íûå êîýôôèöèåíòû ìîäóëÿ + + Ñòðóêòóðà, ñîäåðæàùàÿ âñå êàëèáðîâî÷íûå êîýôôèöèåíòû, êîòîðûå + èñïîëüçóþòñÿ ìîäóëåì L-502/E-502 */ +typedef struct { + /** Êàëèáðîâî÷íûå êîýôôèöèåíòû ÀÖÏ */ + t_x502_cbr_coef adc[X502_ADC_RANGE_CNT]; + /** Êàëèáðîâî÷íûå êîýôôèöèåíòû ÖÀÏ */ + t_x502_cbr_coef dac[X502_DAC_CH_CNT_MAX]; + /** Êàëèáðîâî÷íûå êîýôôèöèåíòû ôèëüòðà êîððåêöèè À×Õ äëÿ ÖÀÏ LTA37 */ + t_x502_afc_coef afc_coef; +} t_x502_cbr; + +/** @brief Èíôîðìàöèÿ î ìîäóëå L-502/E-502 + + Ñòðóêòóðà, ñîäåðæàùàÿ ïîñòîÿííóþ èíôîðìàöèÿ î ìîäóëå L-502/E-502, êîòîðàÿ êàê ïðàâèëî + íå èçìåíÿåòñÿ ïîñëå îòêðûòèÿ + Âíèìàíèå! Áèíàðíàÿ ñîâìåñòèìîñü ìåæäó âåðñèÿìè áèáëèîòåê íå ãàðàíòèðóåòñÿ! + Èñïîëüçóåòå òîëüêî òå âåðñèè áèáëèîòåê ñ êîòîðûìè áûëà ñêîìïèëèðîâàí ïðîãðàììà! +*/ +typedef struct { + char name[X502_DEVNAME_SIZE]; /**< Íàçâàíèå óñòðîéñòâà ("L502" èëè "E502" èëè "E16") */ + char serial[X502_SERIAL_SIZE]; /**< Ñåðèéíûé íîìåð */ + + uint32_t devflags; /**< Ôëàãè èç #t_x502_dev_flags, îïèñûâàþùèå íàëè÷èå + â ìîäóëå îïðåäåëåííûõ îïöèé */ + uint16_t fpga_ver; /**< Âåðñèÿ ÏËÈÑ (ñòàðøèé áàéò - ìàæîðíàÿ, ìëàäøèé - ìèíîðíàÿ) */ + uint8_t plda_ver; /**< Âåðñèÿ ÏËÈÑ, óïðàâëÿþùåãî àíàëîãîâîé ÷àñòüþ */ + uint8_t board_rev; /**< Ðåâèçèÿ ïëàòû */ + uint32_t mcu_firmware_ver; /**< Âåðñèÿ ïðîøèâêè êîíòðîëëåðà Cortex-M4. Äåéñòâèòåëüíà òîëüêî äëÿ ìîäóëÿ E-502 */ + uint32_t flash_size; /**< Ðàçìåð âíåøíåé flash-ïàìÿòè â áàéòàõ */ + uint8_t factory_mac[X502_MAC_ADDR_SIZE]; /**< Çàâîäñêîé MAC-àäðåñ --- äåéñòâèòåëåí òîëüêî äëÿ + óñòðîéñòâ ñ Ethernet-èíòåðôåéñîì */ + t_x502_cbr cbr; /**< Çàâîäñêèå êàëèáðîâî÷íûå êîýôôèöèåíòû (èç Flash-ïàìÿòè) */ +} t_x502_info; + +/** @} */ + + +/** @addtogroup func_list Ôóíêöèè + @{ **/ + +/***************************************************************************//** + @addtogroup func_hnd Ôóíêöèè äëÿ ñîçäàíèÿ è îñâîáîæäåíèÿ îïèñàòåëÿ ìîäóëÿ + @{ +*******************************************************************************/ + +/***************************************************************************//** + @brief Ñîçäàíèå îïèñàòåëÿ ìîäóëÿ + + Ñîçäàíèå îïèñàòåëÿ ìîäóëÿ, äëÿ ïîñëåäóþùåé ðàáîòû ñ ìîäóëåì E-502 èëè L-502. +  ñëó÷àå óñïåøíîãî âûäåëåíèÿ ïàìÿòè èíèöèàëèçèðóåò ïîëÿ îïèñàòåëÿ + çíà÷åíèÿìè ïî óìîë÷àíèþ. + @return NULL â ñëó÷àå îøèáêè, èíà÷å - îïèñàòåëü ìîäóëÿ +*******************************************************************************/ +X502_EXPORT(t_x502_hnd) X502_Create(void); + +/***************************************************************************//** + @brief Îñâîáîæäåíèå îïèñàòåëÿ ìîäóëÿ + + Îñâîáîæäåíèå ïàìÿòè, âûäåëåííîé ïîä îïèñàòåëü ìîäóëÿ ñ ïîìîùüþ X502_Create(). + Ïîñëå ýòîãî îïèñàòåëü óæå èñïîëüçîâàòü íåëüçÿ, íåçàâèñèìî îò âîçâðàùåííîãî + çíà÷åíèÿ! + @param[in] hnd Îïèñàòåëü óñòðîéñòâà + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) X502_Free(t_x502_hnd hnd); +/** @} */ + + + + + + + +/***************************************************************************//** + @addtogroup func_devrec Ôóíêöèè äëÿ ðàáîòû ñ çàïèñÿìè îá óñòðîéñòâå + @{ +*******************************************************************************/ + +/***************************************************************************//** + @brief Îòêðûòü ñîåäèíåíèå ñ ìîäóëåì ïî çàïèñè î óñòðîéñòâå + + Ôóíêöèÿ óñòàíàâëèâàåò ñîåäèíåíèå ñ ìîäóëåì E-502 èëè L-502 ïî çàïèñè îá ýòîì óñòðîéñòâå. + Íåîáõîäèìûå äåéñòâèÿ çàâèñÿò îò òîãî, íà êàêîå óñòðîéñòâî ïîäêëþ÷åííîå ïî + êàêîìó èíòåðôåéñó ññûëàåòñÿ çàïèñü. Ñàìè çàïèñè ñîçäàþòñÿ ñïåöèàëüíûìè + ôóíêöèÿìè (ñâîè äëÿ êàæäîãî òèïà ìîäóëÿ è èíòåðôåéñà ïîäêëþ÷åíèÿ) è íå + äîëæíû èçìåíÿòüñÿ ïîëüçîâàòåëåì âðó÷íóþ. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] devrec Çàïèñü î óñòðîéñòâå, ñîäåðæàùàÿ íåîáõîäèìóþ èíôîðìàöèþ + äëÿ óñòàíîâëåíèÿ ñ íèì ñâÿçè + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_OpenByDevRecord(t_x502_hnd hnd, const t_x502_devrec *devrec); + +/***************************************************************************//** + @brief Îñâîáîæäåíèå çàïèñåé îá óñòðîéñòâàõ + + Ôóíêöèÿ î÷èùàåò ðåñóðñû, âûäåëåííûå ïðè èíèöèàëèçàöèè çàïèñè î óñòðîéñòâå + ïîä èíôîðìàöèþ, íåîáõîäèìóþ äëÿ îòêðûòèÿ óñòðîéñòâà. + Äàííàÿ ôóíêöèÿ äîëæíà âûçûâàòüñÿ ïîñëå èíèöèàëèçàöèè + çàïèñè î óñòðîéñòâå îäíîé èç ñîîòâåòñòâóþùèõ ôóíêöèé ïîñëå òîãî, êîãäà + çàïèñü óæå íå íóæíà. Ïîñëå óñòàíîâêè ñâÿçè ñ óñòðîéñòâîì ÷åðåç + X502_OpenByDevRecord() çàïèñü íå èñïîëüçóåòñÿ â äàëüíåéøåì è åå ìîæíî + ïðè æåëàíèè ñðàçó îñâîáîäèòü, íå çàêðûâàÿ ñîåäèíåíèÿ ñ óñòðîéñòâîì. + Ôóíêöèÿ ìîæåò î÷èñòèòü ñðàçó íåñêîëüêî çàïèñåé èç ìàññèâà (åñëè î÷èùàåòñÿ + îäíà, òî â êà÷åñòâå ðàçìåðà äîïóñòèìî óêàçûâàòü 1). + + @param[in] list Ìàññèâ çàïèñåé î óñòðîéñòâå èëè óêàçàòåëü íà åäèíñòâåííóþ + çàïèñü, ðåñóðñû êîòîðîé (êîòîðûõ) íóæíî îñâîáîäèòü. + @param[in] size Êîëè÷åñòâî çàïèñåé â ìàññèâå + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_FreeDevRecordList(t_x502_devrec *list, uint32_t size); + +/** @} */ + + +/***************************************************************************//** + @addtogroup func_open Ôóíêöèè äëÿ îòêðûòèÿ è ïîëó÷åíèÿ èíôîðìàöèè î ìîäóëå + @{ +*******************************************************************************/ + +/***************************************************************************//** + @brief Çàêðûòèå ñîåäèíåíèÿ ñ ìîäóëåì + + Ôóíêöèÿ ðàçðûâàåò ñîåäèíåíèå ñ ìîäóëåì E-502/L-502, åñëè îíî áûëî ðàíåå óñòàíîâëåíî + (â ïðîòèâíîì ñëó÷àå íè÷åãî íå äåëàåò). + Îïèñàòåëü ìîäóëÿ íå îñâîáîæäàåòñÿ. + Ïàìÿòü ïîä îïèñàòåëü ìîäóëÿ äîëæíà áûòü îñâîáîæäåíà âûçîâîì X502_Free(). + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_Close(t_x502_hnd hnd); + + +/***************************************************************************//** + @brief Ïðîâåðêà, îòêðûòî ëè ñîåäèíåíèå ñ ìîäóëåì + + Ôóíêöèÿ ïðîâåðÿåò, îòêðûòî ëè â äàííûé ìîìåíò ñîåäèíåíèå ñ ìîäóëåì. Åñëè + ñîåäèíåíèå îòêðûòî, òî ôóíêöèÿ âîçâðàùàåò #X502_ERR_OK. Åñëè æå çàêðûòî, òî + ôóíêöèÿ âîçâðàùàåò îøèáêó #X502_ERR_DEVICE_NOT_OPENED. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_IsOpened(t_x502_hnd hnd); + + + +/***************************************************************************//** + @brief Ïîëó÷åíèå èíôîðìàöèè î ìîäóëå + + Ïîëó÷åíèå èíôîðìàöèè î ìîäóëå L-502/E-502, ñ êîòîðûì óñòàíîâëåíà ñâÿçü. + Âíèìàíèå! Ïðè èñïîëüçîâàíèè ýòîé ôóíêöèè (êàê è ëþáîé äðóãîé èç ýòîãî api) + âàæíî óáåäèòüñÿ ÷òî ïðè ñáîðêå èñïîëíÿåìîãî ôàéëà, èñïîëüçóåòñÿ çàãîëîâî÷íûé ôàéë + òîé æå âåðñèè ÷òî è áèíàðíûé ôàéë ñàìîé áèáëèîòåêè (x502api.h == x502api.dll)! + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] info Èíôîðìàöèÿ î ìîäóëå (ñìîòðè îïèñàíèå òèïà #t_x502_info). + @return Êîä îøèáêè. +*******************************************************************************/ +#ifdef _WIN32 + #pragma deprecated(X502_GetDevInfo) +#else + __attribute__((__deprecated__)) +#endif +X502_EXPORT(int32_t) X502_GetDevInfo(t_x502_hnd hnd, t_x502_info* info); + +/***************************************************************************//** + @brief Ïîëó÷åíèå èíôîðìàöèè î ìîäóëå + + Ïîëó÷åíèå èíôîðìàöèè î ìîäóëå L-502/E-502, ñ êîòîðûì óñòàíîâëåíà ñâÿçü. + Âíèìàíèå! Ïðè èñïîëüçîâàíèè ýòîé ôóíêöèè (êàê è ëþáîé äðóãîé èç ýòîãî api) + âàæíî óáåäèòüñÿ ÷òî ïðè ñáîðêå èñïîëíÿåìîãî ôàéëà, èñïîëüçóåòñÿ çàãîëîâî÷íûé ôàéë + òîé æå âåðñèè ÷òî è áèíàðíûé ôàéë ñàìîé áèáëèîòåêè (x502api.h == x502api.dll)! + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] info Èíôîðìàöèÿ î ìîäóëå (ñìîòðè îïèñàíèå òèïà #t_x502_info). + @param[in] size Ðàçìåð sizeof(t_x502_info). + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_GetDevInfo2(t_x502_hnd hnd, t_x502_info* info, uint32_t size); + +/** @} */ + +/***************************************************************************//** + @addtogroup func_config Ôóíêöèè äëÿ èçìåíåíèÿ íàñòðîåê ìîäóëÿ + @{ +*******************************************************************************/ + +/***************************************************************************//** + @brief Ïåðåäà÷à óñòàíîâëåííûõ íàñòðîåê â ìîäóëü + + Ôóíêöèÿ âûïîëíÿåò çàïèñü òåêóùèõ íàñòðîåê (êîòîðûå áûëè óñòàíîâëåíû + ñ ïîìîùüþ ôóíêöèé X502_SetXXX) â ìîäóëü. + Äîëæíà âûçûâàòüñÿ ïåðåä çàïóñêîì ïîòîêà äàííûõ. + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] flags Ôëàãè (ðåçåðâ - äîëæíî áûòü ðàâíî 0). + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_Configure(t_x502_hnd hnd, uint32_t flags); + + +/***************************************************************************//** + @brief Óñòàíîâêà ïàðàìåòðîâ ëîãè÷åñêîãî êàíàëà + + Ôóíêöèÿ óñòàíàâëèâàåò ïàðàìåòðû çàäàííîãî ëîãè÷åñêîãî êàíàëà â ëîãè÷åñêîé + òàáëèöå ÀÖÏ. + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] lch Íîìåð ëîãè÷åñêîãî êàíàëà. + (îò 0 äî #X502_LTABLE_MAX_CH_CNT-1 èëè äî #E16_LTABLE_MAX_CH_CNT-1 äëÿ E16) + @param[in] phy_ch Íîìåð ôèçè÷åñêîãî êàíàëà ÀÖÏ, íà÷èíàÿ ñ 0 + (0-15 äëÿ äèôôåðåíöèàëüíîãî ðåæèìà, + 0-31 äëÿ ðåæèìà ñ îáùåé çåìëåé) + @param[in] mode Ðåæèì èçìåðåíèÿ êàíàë ÀÖÏ (çíà÷åíèå òèïà #t_x502_lch_mode) + @param[in] range Äèàïàçîí èçìåðåíèÿ êàíàëà (çíà÷åíèå òèïà #t_x502_adc_range) + @param[in] avg Êîýôôèöèåíò óñðåäíåíèÿ ïî êàíàëó (íå ðåàëèçîâàíî â E16). Íóëåâîå çíà÷åíèå + ñîîòâåòñòâóåò çíà÷åíèþ êîýôôèöèåíòà, îïðåäåëåííîãî + áèáëèîòåêîé. Äëÿ ÿâíîãî çàäàíèÿ êîýôôèöèåíòà óñðåäíåíèÿ + íóæíî ïåðåä çíà÷åíèå îò 1 (îòñóòñòâèå óñðåäíåíèÿ) äî + #X502_LCH_AVG_SIZE_MAX. +  ñëó÷àå åñëè çíà÷åíèå óñðåäíåíèÿ ïðåâûøàåò äåëèòåëü + ÷àñòîòû, òî ýòî çíà÷åíèå áóäåò ñêîððåêòèðîâàíî + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_SetLChannel(t_x502_hnd hnd, uint32_t lch, uint32_t phy_ch, + uint32_t mode, uint32_t range, uint32_t avg); + + +/***************************************************************************//** + @brief Ïîëó÷åíèå ìàññèâà äèàïàçîíîâ ìîäóëÿ E16 + + Âîçâðàùàåì óêàçàòåëü íà ìàññèâ: + double ranges[] = {10, 2.5, 0.625, 0.15625}; + + @param[in] ranges_len Óêàçàòåëü êóäà çàïèñûâàåò ðàçìåð âîçâðàùàåìîãî ìàññèâà + @return Óêàçàòåëü íà ìàññèâ double ðàçìåðîì ranges_len èëè NULL â ñëó÷àå îøèáêè. +*******************************************************************************/ +X502_EXPORT(double const*) E16_GetAdcRanges(uint32_t *ranges_len); + +/***************************************************************************//** + @brief Ïîëó÷åíèå ìàññèâà äèàïàçîíîâ ìîäóëÿ E502 + + Âîçâðàùàåì óêàçàòåëü íà ìàññèâ: + double ranges[] = {10., 5., 2., 1., 0.5, 0.2}; + + @param[in] ranges_len Óêàçàòåëü êóäà çàïèñûâàåò ðàçìåð âîçâðàùàåìîãî ìàññèâà + @return Óêàçàòåëü íà ìàññèâ double ðàçìåðîì ranges_len èëè NULL â ñëó÷àå îøèáêè. +*******************************************************************************/ +X502_EXPORT(double const*) E502_GetAdcRanges(uint32_t *ranges_len); + +/***************************************************************************//** + @brief Ïîëó÷åíèå ìàññèâà äèàïàçîíîâ ìîäóëÿ + + Äëÿ ìîäóëÿ E16: + double ranges[] = {10, 2.5, 0.625, 0.15625}; + Äëÿ ìîäóëÿ E502: + double ranges[] = {10., 5., 2., 1., 0.5, 0.2}; + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] ranges_len Óêàçàòåëü êóäà çàïèñûâàåò ðàçìåð âîçâðàùàåìîãî ìàññèâà + @return Óêàçàòåëü íà ìàññèâ double ðàçìåðîì ranges_len èëè NULL â ñëó÷àå îøèáêè. +*******************************************************************************/ +X502_EXPORT(double const*) X502_GetAdcRanges(t_x502_hnd hnd, uint32_t *ranges_len); + +/***************************************************************************//** + @brief Óñòàíîâêà êîëè÷åñòâà ëîãè÷åñêèõ êàíàëîâ + + Ôóíêöèÿ óñòàíàâëèâàåò êîëè÷åñòâî ëîãè÷åñêèõ êàíàëîâ â ëîãè÷åñêîé òàáëèöå ÀÖÏ. + @param[in] hnd Îïèñàòåëü ìîäóëÿ + @param[in] lch_cnt Êîëè÷åñòâî ëîãè÷åñêèõ êàíàëîâ + (îò 1 äî #X502_LTABLE_MAX_CH_CNT èëè äî #E16_LTABLE_MAX_CH_CNT äëÿ E16) + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) X502_SetLChannelCount(t_x502_hnd hnd, uint32_t lch_cnt); + + +/***************************************************************************//** + @brief Ïîëó÷åíèå êîëè÷åñòâà ëîãè÷åñêèõ êàíàëîâ + + Ôóíêöèÿ âîçâðàùàåò óñòàíîâëåííîå ðàíåå ñ ïîìîùüþ X502_SetLChannelCount() + êîëè÷åñòâî ëîãè÷åñêèõ êàíàëîâ â óïðàâëÿþùåé òàáëèöå ÀÖÏ. + @param[in] hnd Îïèñàòåëü ìîäóëÿ + @param[out] lch_cnt Êîëè÷åñòâî ëîãè÷åñêèõ êàíàëîâ + @return Êîä îøèáêè +*******************************************************************************/ +X502_EXPORT(int32_t) X502_GetLChannelCount(t_x502_hnd hnd, uint32_t* lch_cnt); + +/***************************************************************************//** + @brief Óñòàíîâêà äåëèòåëÿ ÷àñòîòû ñáîðà äëÿ ÀÖÏ + + ×àñòîòà ñáîðà ÀÖÏ ïîëó÷àåòñÿ êàê ðåçóëüòàò äåëåíèÿ îïîðíîé ÷àñòîòû + ñèíõðîíèçàöèè (êàê â ñëó÷àå âíåøíåé, òàê è âíóòðåííåé) íà äåëèòåëü, + óñòàíàâëèâàåìûé ýòîé ôóíêöèåé. + + Àëüòåðíàòèâîé ýòîé ôóíêöèè ñëóæèò X502_SetAdcFreq(), êîòîðàÿ ðàññ÷èòûâàåò + ýòîò äåëèòåëü íà îñíîâàíèè ïåðåäàííîé òðåáóåìîé ÷àñòîòû ñáîðà ÀÖÏ. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] adc_freq_div Äåëèòåëü ÷àñòîòû ÀÖÏ (îò 1 äî #X502_ADC_FREQ_DIV_MAX). + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_SetAdcFreqDivider(t_x502_hnd hnd, uint32_t adc_freq_div); + +/***************************************************************************//** + @brief Óñòàíîâêà çíà÷åíèÿ ìåæêàäðîâîé çàäåðæêè äëÿ ÀÖÏ + + Ôóíêöèÿ óñòàíàâëèâàåò ìåæêàäðîâóþ çàäåðæêó äëÿ ÀÖÏ, òî åñòü êîëè÷åñòâî + ïåðèîäîâ îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè, êîòîðîå áóäåò ïðîïóùåíî ïîñëå + ïðîâåäåíèÿ èçìåðåíèÿ ïîñëåäíåãî êàíàëà ëîãè÷åñêîé òàáëèöû äî ïðîâåäåíèÿ + èçìåðåíèÿ, ñîîòâåòñòâóþùåãî ïåðâîìó ëîãè÷åñêîìó êàíàëó ñëåäóþùåãî êàäðà. + + Àëüòåðíàòèâîé ìîæåò ÿâëÿòüñÿ ôóíêöèÿ X502_SetAdcFreq(), êîòîðàÿ ðàññ÷èòûâàåò + çíà÷åíèå ìåæêàäðîâîé çàäåðæêè ïî çàäàííûì ïàðàìåòðàì ÷àñòîòû ñáîðà è ÷àñòîòû + ñëåäîâàíèÿ êàäðîâ (÷àñòîòû ñáîðà íà ëîãè÷åñêèé êàíàë). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] delay Çíà÷åíèå ìåæêàäðîâîé çàäåðæêè (îò 0 äî + #X502_ADC_INTERFRAME_DELAY_MAX) + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_SetAdcInterframeDelay(t_x502_hnd hnd, uint32_t delay); + +/***************************************************************************//** + @brief Óñòàíîâêà äåëèòåëÿ ÷àñòîòû ñèíõðîííîãî ââîäà ñ öèôðîâûõ ëèíèé. + + ×àñòîòà ñèíõðîííîãî ââîäà äàííûõ ñ öèôðîâûõ âõîäîâ ïîëó÷àåòñÿ êàê ðåçóëüòàò + äåëåíèÿ îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè íà äåëèòåëü, óñòàíàâëèâàåìûé ýòîé + ôóíêöèåé. + + Àëüòåðíàòèâîé ýòîé ôóíêöèè ñëóæèò X502_SetDinFreq(), êîòîðàÿ ðàññ÷èòûâàåò + ýòîò äåëèòåëü íà îñíîâàíèè ïåðåäàííîé òðåáóåìîé ÷àñòîòû ñèíõðîííîãî ââîäà + ñ öèôðîâûõ ëèíèé. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] din_freq_div Äåëèòåëü ÷àñòîòû ñèíõðîííîãî ââîäà ñ öèôðîâûõ ëèíèé + (îò 1 äî #X502_DIN_FREQ_DIV_MAX). + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_SetDinFreqDivider(t_x502_hnd hnd, uint32_t din_freq_div); + + + +/***************************************************************************//** + @brief Óñòàíîâêà äåëèòåëÿ ÷àñòîòû ñèíõðîííîãî âûâîäà + + ×àñòîòà ñèíõðîííîãî âûâîäà äàííûõ ïîëó÷àåòñÿ êàê ðåçóëüòàò äåëåíèÿ îïîðíîé + ÷àñòîòû ñèíõðîíèçàöèè íà äåëèòåëü, óñòàíàâëèâàåìûé ýòîé ôóíêöèåé. + Èñïîëüçóåòñÿ îáùàÿ ÷àñòîòà âûâîäà äëÿ êàæäîãî êàíàëà ÖÀÏ è äëÿ öèôðîâûõ + ëèíèé (âûâîä îñóùåñòâëÿåòñÿ ïàðàëëåëüíî). ×àñòîòà âûâîäà íå ìîæåò áûòü + áîëüøå ïîëîâèíû îïîðíîé ÷àñòîòû. + + Àëüòåðíàòèâîé ýòîé ôóíêöèè ñëóæèò X502_SetOutFreq(), êîòîðàÿ ðàññ÷èòûâàåò + ýòîò äåëèòåëü íà îñíîâàíèè ïåðåäàííîé òðåáóåìîé ÷àñòîòû ñèíõðîííîãî âûâîäà. + + @note Äëÿ ìîäóëÿ L-502, ÷òîáû áûëà âîçìîæíîñòü óñòàíîâèòü äåëèòåëü, îòëè÷íûé îò + #X502_OUT_FREQ_DIV_DEFAULT, íåîáõîäèìî îáíîâèòü ïðîøèâêó ÏËÈÑ äî âåðñèè 0.5 èëè âûøå. + Äëÿ ìîäóëÿ E-502 âîçìîæíîñòü âñåãäà ïîääåðæèâàåòñÿ. Ïðîâåðèòü ïðîãðàììíî íàëè÷èå + äàííîé âîçìîæíîñòè ìîæíî ñ ïîìîùüþ ôóíêöèè X502_CheckFeature(). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] out_freq_div Äåëèòåëü ÷àñòîòû ñèíõðîííîãî âûâîäà + (îò #X502_OUT_FREQ_DIV_MIN äî #X502_OUT_FREQ_DIV_MAX). + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_SetOutFreqDivider(t_x502_hnd hnd, uint32_t out_freq_div); + + + +/***************************************************************************//** + @brief Óñòàíîâêà ÷àñòîòû ñáîðà ÀÖÏ + + Ôóíêöèÿ ïîäáèðàåò äåëèòåëü ÷àñòîòû ÀÖÏ òàê, ÷òîáû ïîëó÷åííàÿ ÷àñòîòà ñáîðà + áûëà íàèáîëåå áëèçêà ê óêàçàííîé â ïàðàìåòðå f_acq. Ôóíêöèÿ âîçâðàùàåò + â ýòîì æå ïàðàìåòðå ðåàëüíóþ ÷àñòîòó, êîòîðàÿ áûëà óñòàíîâëåíà. + + Òàê æå ôóíêöèÿ ìîæåò ïîäîáðàòü çíà÷åíèå ìåæêàäðîâîé çàäåðæêè òàê, ÷òîáû + ÷àñòîòà ñëåäîâàíèÿ êàäðîâ (÷àñòîòà ñáîðà íà ëîãè÷åñêèé êàíàë) áûëà íàèáîëåå + áëèçêà ê óêàçàííîìó çíà÷åíèþ. + Äëÿ ýòîãî ñëåäóåò ïåðåäàòü òðåáóåìîå çíà÷åíèå â ïåðåìåííîé f_frame (â íåé + òàêæå ïî çàâåðøåíèþ áóäåò âîçâðàùåíî çíà÷åíèå óñòàíîâëåííîé ÷àñòîòû). + Åñëè â êà÷åñòâå f_frame ïåðåäàí íóëåâîé óêàçàòåëü, òî áóäåò óñòàíîâëåíà + íóëåâàÿ ìåæêàäðîâàÿ çàäåðæêà. + + Åñëè íåîáõîäèìî èçìåíèòü çíà÷åíèå îïîðíîé ÷àñòîòû, òî äàííàÿ ôóíêöèÿ äîëæíà + áûòü âûçâàíà ïîñëå X502_SetSyncMode() è X502_SetRefFreq() / X502_SetExtRefFreqValue(), + â ïðîòèâíîì ñëó÷àå ïîëó÷åííûå äåëèòåëè áóäóò äàâàòü íåâåðíîå çíà÷åíèå ÷àñòîòû. + + Åñëè óñòàíàâëèâàåòñÿ ÷àñòîòà êàäðîâ, òî ôóíêöèÿ äîëæíà âûçûâàòüñÿ ïîñëå + òîãî, êàê áûëî çàäàííî íóæíîå êîëè÷åñòâî ëîãè÷åñêèõ êàíàëîâ â óïðàâëÿþùåé + òàáëèöå ñ ïîìîùüþ X502_SetLChannelCount(). + + Ïðè èñïîëüçîâàíèè âíåøíåé îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè ýòà ôóíêöèÿ áóäåò + äàâàòü âåðíûé ðåçóëüòàò, òîëüêî åñëè ýòà âíåøíÿÿ ÷àñòîòà ñîîòâåòñòâóåò + çíà÷åíèþ, óñòàíîâëåííîìó ñ ïîìîùüþ X502_SetRefFreq(). + + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in,out] f_acq Íà âõîäå ïðèíèìàåò òðåáóåìîå çíà÷åíèÿ ÷àñòîòû ñáîðà + ÀÖÏ â Ãåðöàõ. Íà âûõîäå âîçâðàùàåò ðåàëüíî + óñòàíîâëåííîå çíà÷åíèå ÷àñòîòû. + @param[in,out] f_frame Íà âõîäå ïðèíèìàåò òðåáóåìîå çíà÷åíèå ÷àñòîòû ñáîðà + êàäðîâ (÷àñòîòû ñáîðà íà ëîãè÷åñêèé êàíàë) ÀÖÏ + â Ãåðöàõ. Íà âûõîäå âîçâðàùàåò ðåàëüíî + óñòàíîâëåííîå çíà÷åíèå. Åñëè ïåðåäàí íóëåâîé + óêàçàòåëü, òî óñòàíàâëèâàåò ìàêñèìàëüíóþ ÷àñòîòó + ñáîðà êàäðîâ (íóëåâóþ ìåæêàäðîâóþ çàäåðæêó). + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_SetAdcFreq(t_x502_hnd hnd, double *f_acq, double *f_frame); + + +/***************************************************************************//** + @brief Óñòàíîâêà ÷àñòîòû ñèíõðîííîãî ââîäà ñ öèôðîâûõ âõîäîâ + + Ôóíêöèÿ ïîäáèðàåò äåëèòåëü ÷àñòîòû ââîäà çíà÷åíèé ñ öèôðîâûõ âõîäîâ òàê, ÷òîáû + ïîëó÷åííàÿ ÷àñòîòà ââîäà áûëà íàèáîëåå áëèçêà ê óêàçàííîé. Ôóíêöèÿ âîçâðàùàåò + â ýòîì æå ïàðàìåòðå ðåàëüíóþ ÷àñòîòó, êîòîðàÿ áûëà óñòàíîâëåíà. + + Åñëè íåîáõîäèìî èçìåíèòü çíà÷åíèå îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè, òî äàííàÿ + ôóíêöèÿ äîëæíà áûòü âûçâàíà ïîñëå X502_SetSyncMode() è + X502_SetRefFreq()/ X502_SetExtRefFreqValue(), â ïðîòèâíîì ñëó÷àå ïîëó÷åííûé + äåëèòåëü áóäåò äàâàòü íåâåðíîå çíà÷åíèå ÷àñòîòû. + + Ïðè èñïîëüçîâàíèè âíåøíåé îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè ýòà ôóíêöèÿ áóäåò + äàâàòü âåðíûé ðåçóëüòàò, òîëüêî åñëè ýòà âíåøíÿÿ ÷àñòîòà ñîîòâåòñòâóåò + çíà÷åíèþ, óñòàíîâëåííîìó ñ ïîìîùüþ X502_SetRefFreq(). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in,out] f_din Íà âõîäå ïðèíèìàåò òðåáóåìîå çíà÷åíèÿ ÷àñòîòû ââîäà + ñ öèôðîâûõ âõîäîâ â Ãåðöàõ. Íà âûõîäå âîçâðàùàåò + ðåàëüíî óñòàíîâëåííîå çíà÷åíèå ÷àñòîòû. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_SetDinFreq(t_x502_hnd hnd, double *f_din); + + + +/***************************************************************************//** + @brief Óñòàíîâêà ÷àñòîòû ñèíõðîííîãî âûâîäà + + Ôóíêöèÿ ïîäáèðàåò äåëèòåëü ÷àñòîòû ñèíõðîííîãî âûâîäà òàê, ÷òîáû + ïîëó÷åííàÿ ÷àñòîòà áûëà íàèáîëåå áëèçêà ê óêàçàííîé. Ôóíêöèÿ âîçâðàùàåò + â ýòîì æå ïàðàìåòðå ðåàëüíóþ ÷àñòîòó, êîòîðàÿ áûëà óñòàíîâëåíà. + + Åñëè íåîáõîäèìî èçìåíèòü çíà÷åíèå îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè, òî äàííàÿ + ôóíêöèÿ äîëæíà áûòü âûçâàíà ïîñëå X502_SetSyncMode() è + X502_SetRefFreq() / X502_SetExtRefFreqValue(), â ïðîòèâíîì ñëó÷àå + ïîëó÷åííûé äåëèòåëü áóäåò äàâàòü íåâåðíîå çíà÷åíèå ÷àñòîòû. + + Ïðè èñïîëüçîâàíèè âíåøíåé îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè ýòà ôóíêöèÿ áóäåò + äàâàòü âåðíûé ðåçóëüòàò, òîëüêî åñëè ýòà âíåøíÿÿ ÷àñòîòà ñîîòâåòñòâóåò + çíà÷åíèþ, óñòàíîâëåííîìó ñ ïîìîùüþ X502_SetRefFreq(). + + @note Äëÿ ìîäóëÿ L-502, ÷òîáû áûëà âîçìîæíîñòü óñòàíîâèòü ÷àñòîòó, îòëè÷íóþ îò + îïîðíîé ÷àñòîòû, äåëåííîé íà #X502_OUT_FREQ_DIV_DEFAULT, íåîáõîäèìî îáíîâèòü + ïðîøèâêó ÏËÈÑ äî âåðñèè 0.5 èëè âûøå. + Äëÿ ìîäóëÿ E-502 âîçìîæíîñòü âñåãäà ïîääåðæèâàåòñÿ. + Ïðîâåðèòü ïðîãðàììíî íàëè÷èå äàííîé âîçìîæíîñòè ìîæíî ñ ïîìîùüþ + ôóíêöèè X502_CheckFeature(). + + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in,out] f_dout Íà âõîäå ïðèíèìàåò òðåáóåìîå çíà÷åíèÿ ÷àñòîòû + ñèíõðîííîãî âûâîäà â Ãåðöàõ. Íà âûõîäå âîçâðàùàåò + ðåàëüíî óñòàíîâëåííîå çíà÷åíèå ÷àñòîòû. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_SetOutFreq(t_x502_hnd hnd, double *f_dout); + + + +/***************************************************************************//** + @brief Ïîëó÷èòü òåêóùèå çíà÷åíèÿ ÷àñòîò ñáîðà ÀÖÏ + + Ôóíêöèÿ âîçâðàùàåò òêóùèå óñòàíîâëåííûå äëÿ ìîäóëÿ çíà÷åíèÿ ÷àñòîòû ñáîðà + è ÷àñòîòû êàäðîâ ÀÖÏ (÷àñòîòû íà ëîãè÷åñêèé êàíàë) â Ãåðöàõ, êîòîðûå áûëè + óñòàíîâëåíû äî ýòîãî ñ ïîìîùüþ X502_SetAdcFreq() èëè ñ ïîìîùüþ ôóíêöèé + X502_SetAdcFreqDivider() / X502_SetAdcInterframeDelay(). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] f_acq Åñëè íå NULL, òî íà âûõîäå âîçâðàùàåòñÿ òåêóùåå + çíà÷åíèå ÷àñòîòû ñáîðà ÀÖÏ. + @param[out] f_frame Åñëè íå NULL, òî íà âûõîäå âîçâðàùàåòñÿ òåêóùåå + çíà÷åíèå ÷àñòîòû êàäðîâ ÀÖÏ. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_GetAdcFreq(t_x502_hnd hnd, double *f_acq, double *f_frame); + + +/***************************************************************************//** + @brief Óñòàíîâêà çíà÷åíèÿ âíóòðåííåé îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè + + Ôóíêöèÿ çàäàåò çíà÷åíèå âíóòðåííåé îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè, îò êîòîðîé + ïîëó÷àþòñÿ âñå ÷àñòîòû ñèíõðîííîãî ââîäà/âûâîäà ïîñðåäñòâîì äåëåíèÿ + íà îïðåäåëåííûé äåëèòåëü. + + Äàííàÿ ôóíêöèÿ ïðè âíóòðåííåé îïîðíîé ÷àñòåòå âûáèðàåò îäíó èç äâóõ äîñòóïíûõ + ÷àñòîò â 2ÌÃö èëè 1.5 ÌÃö(2ÌÃö ÿâëÿåòñÿ çíà÷åíèåì ïî óìîë÷àíèþ), äëÿ çàäàíèÿ + êîòîðûõ ìîæíî ââåäåíû êîíñòàíòû èç #t_x502_ref_freq. + + Ïðè èñïîëüçîâàíèè âíåøíåé îïîðíîé ÷àñòîòû ñëåäóåò èñïîëüçîâàòü + X502_SetExtRefFreqValue(). + + Äëÿ ìîäóëÿ E-502 âûâîä íà ÖÀÏ ïðè îïîðíîé ÷àñòîòå 1.5 ÌÃö ðàáîòàåò òîëüêî + äëÿ âåðñèè ïðîøèâêè PLDA 1 èëè âûøå. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] freq Çíà÷åíèå èç #t_x502_ref_freq, êîòîðîå çàäàåò + âûáðàííóþ îïîðíóþ ÷àñòîòó. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_SetRefFreq(t_x502_hnd hnd, uint32_t freq); + +/***************************************************************************//** + @brief Óñòàíîâêà çíà÷åíèÿ âíåøíåé îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè + + Ïðè óñòàíîâêå âíåøíåé îïîðíîé ÷àñòîòû (âûçîâ X502_SetSyncMode() ñî çàí÷åíèåì, + îòëè÷íûì îò #X502_SYNC_INTERNAL) äàííàÿ ôóíêöèÿ ïîçâîëÿåò çàäàòü + ÷àñòîòó âíåøíåé îïîðíîé ÷àñòîòû, êîòîðîÿ ìîæåò áûòü ëþáàÿ, íî íå ïðåâûøàòü + 1.5 ÌÃö. + + Äàííàÿ ôóíêöèÿ íå âëèÿåò íà íàñòðîéêè ñàìîãî ìîäóëÿ, îäíàêî óñòàíîâêà êîððåêòíîãî + çíà÷åíèÿ ïîçâîëÿåò ïðàâèëüíî óñòàíîâèòü íóæíóþ ÷àñòîòó ñáîðà ôóíêöèÿìè + X502_SetAdcFreq(), X502_SetDinFreq() è X502_SetOutFreq(), à òàêæå êîððåêòíî + ðàññ÷èòàòü çíà÷åíèÿ ïî óìîë÷àíèþ äëÿ ðàçìåðà áóôåðà è øàãà ïåðåäà÷è äàííûõ + ìåæäó ìîäóëåì è ÏÊ. + + Äàííàÿ ôóíêöèÿ äîñòóïíà â áèáëèîòåêå âåðñèè 1.1.4 èëè âûøå. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] freq Çíà÷åíèå âíåøíåé îïîðíîé ÷àñòîòû â Ãö. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_SetExtRefFreqValue(t_x502_hnd hnd, double freq); + +/***************************************************************************//** + @brief Óñòàíîâêà çíà÷åíèÿ äëÿ àíàëîãîâîé ñèíõðîíèçàöèè (òîëüêî äëÿ E16) + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] phy_ch Íîìåð ôèçè÷åñêîãî êàíàëà ÀÖÏ, íà÷èíàÿ ñ 0 + (0-15 äëÿ äèôôåðåíöèàëüíîãî ðåæèìà, + 0-31 äëÿ ðåæèìà ñ îáùåé çåìëåé) + @param[in] mode Ðåæèì èçìåðåíèÿ êàíàë ÀÖÏ (çíà÷åíèå òèïà #t_x502_lch_mode) + @param[in] range Äèàïàçîí èçìåðåíèÿ êàíàëà (çíà÷åíèå òèïà #t_x502_adc_range) + @param[in] value Çíà÷åíèå â Âîëüòàõ. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_SetAdcSyncStartValue(t_x502_hnd hnd, uint32_t phy_ch, uint32_t mode, uint32_t range, double value); + +/***************************************************************************//** + @brief Ïîëó÷åíèå çíà÷åíèÿ îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè + + Äàííàÿ ôóíêöèÿ âîçâðàùàåò òåêóùåå çíà÷åíèå îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè, + êîòîðîå èñïîëüçóåòñÿ áèáëèîòåêîé â ôóíêöèÿõ X502_SetAdcFreq(), X502_SetDinFreq() + è X502_SetOutFreq(), à òàêæå ïðè ðàññ÷åòå ïàðàìåòðîâ ïåðåäà÷è äàííûõ ìåæäó + ìîäóëåì è ÏÊ. + + Ïðè âíóòðåííåé îïîðíîé ÷àñòîòå èñïîëüçóåòñÿ çíà÷åíèå, óñòàíîâëåííîå + X502_SetRefFreq() (1.5 èëè 2 Ìãö), ïðè âíåøíåé --- ÷àñòîòà, óñòàíîâëåííàÿ + ñ ïîìîùüþ ôóíêöèè X502_SetExtRefFreqValue(). + + Äàííàÿ ôóíêöèÿ äîñòóïíà â áèáëèîòåêå âåðñèè 1.1.4 èëè âûøå. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] freq Çíà÷åíèå âíåøíåé îïîðíîé ÷àñòîòû â Ãö. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_GetRefFreqValue(t_x502_hnd hnd, double *freq); + + + + +/***************************************************************************//** + @brief Óñòàíîâêà ðåæèìà ãåíåðàöèè ÷àñòîòû ñèíõðîíèçàöèè + + Ôóíêöèÿ óñòàíàâëèâàåò êòî áóäåò ãåíåðàòîðîì îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè - + ñàì ìîäóëü èëè áóäåò èñïîëüçîâàòüñÿ âíåøíèé ñèãíàë. + +  ðåæèìå #X502_SYNC_INTERNAL ìîäóëü ñàì áóäåò ãåíåðèðîâàòü äëÿ ñåáÿ + ÷àñòîòó ñèíõðîíèçàöèè ñ ÷àñòîòîé, çàäàííîé X502_SetRefFreq(). + Ïðè ýòîì çàïóñê ãåíåðàöèè áóäåò îñóùåñòâëåí ïî âûçîâó X502_StreamsStart() + èëè ïî óñëîâèþ, çàäàíîìó â X502_SetSyncStartMode(), à îñòàíîâ + ïî X502_StreamsStop(). + +  îñòàëüíûõ ðåæèìàõ ñáîð áóäåò îñóùåñòâëÿòüñÿ ïî âíåøíåìó ñèãíàëó + ñèíõðîíèçàöèè. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] sync_mode Çíà÷åíèå èç #t_x502_sync_mode, îïðåäåëÿþùåå êòî + áóäåò èñòî÷íèêîì ÷àñòîòû ñèíõðîíèçàöèè. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_SetSyncMode(t_x502_hnd hnd, uint32_t sync_mode); + +/***************************************************************************//** + @brief Óñòàíîâêà ðåæèìà çàïóñêà ÷àñòîòû ñèíõðîíèçàöèè + + Ôóíêöèÿ óñòàíàâëèâàåò óñëîâèå çàïóñêà ñèíõðîííîãî ââîäà/âûâîäà äàííûõ. + + Åñëè ñ ïîìîùüþ X502_SetSyncMode() óñòàíîâëåí ðåæèì ñèíõðîíèçàöèè + #X502_SYNC_INTERNAL, òî ïî çàäàííîìó äàííîé ôóíêöèåé óñëîâèþ ìîäóëü íà÷íåò + ãåíåðèðîâàòü ÷àñòîòó ñèíõðîíèçàöèè, â ïðîòèâíîì ñëó÷àå ïî çàäàííîìó óñëîâèþ + ìîäóëü íà÷íåò èñïîëüçîâàòü âíåøíå çàäàííóþ ÷àñòîòó ñèíõðîíèçàöèè + (ò.å. äî âûïîëíåíèÿ óñëîâèÿ ñèãíàë ñèíõðîíèçàöèè íà çàäàííîì âõîäå áóäåò + èãíîðèðîâàòüñÿ). + + Ðåæèìû çàäàíèÿ óñëîâèÿ çàïóñêà ñèíõðîíèçàöèè èìåþò òå æå çíà÷åíèÿ, + ÷òî è ðåæèìû çàäàíèÿ ñàìîé ÷àñòîòû (ñì. òèï #t_x502_sync_mode). +  ñëó÷àå #X502_SYNC_INTERNAL çàïóñê îñóùåñòâëÿåòñÿ ïðè âûïîëíåíèè ôóíêöèè + X502_StreamsStart(), â ïðîòèâíîì ñëó÷àå - ïîñëå âûïîëíåíèÿ + X502_StreamsStart() ìîäóëü íà÷èíàåò îæèäàòü çàäàííîãî äàííîé ôóíêöèåé óñëîâèÿ. + Ò.å. äàæå ïðè çàäàíèè âíåøíèõ èñòî÷íèêîâ ñèíõðîíèçàöèè, âñå ðàâíî íåîáõîäèìî + âûçûâàòü X502_StreamsStart(). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] sync_start_mode Çíà÷åíèå èç #t_x502_sync_mode, îïðåäåëÿþùåå + óñëîâèå çàïóñêà ÷àñòîòû ñèíõðîíèçàöèè. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_SetSyncStartMode(t_x502_hnd hnd, uint32_t sync_start_mode); + + +/***************************************************************************//** + @brief Óñòàíîâèòü ðåæèì ðàáîòû ìîäóëÿ + + Ôóíêöèÿ óñòàíàâëèâàåò ðåæèì ðàáîòû ìîäóëÿ, êîòîðûé îïðåäåëÿåò áóäåò ëè + ïîòîêè äàííûõ îáðàáàòûâàòü ÏËÈÑ èëè ñèãíàëüíûé ïðîöåññîð BlackFin. + Ïðè âêëþ÷åíèè ïèòàíèÿ ìîäóëåì âñåãäà óïðàâëÿåò ÏËÈÑ. + Ïîñëå çàãðóçêè ïðîøèâêè ñ ïîìîùüþ X502_BfLoadFirmware() ìîäóëü ïåðåõîäèò + â ðåæèì óïðàâëåíèÿ ñèãíàëüíûì ïðîöåññîðîì. + + Äàííàÿ ôóíêöèÿ ìîæåò èñïîëüçîâàòüñÿ äëÿ ðó÷íîé óñòàíîâêè ðåæèìà, + íàïðèìåð, äëÿ âîçâðàòà â ðåæèì óïðàâëåíèÿ ÏËÈÑ èëè äëÿ ïåðåêëþ÷åíèÿ â ðåæèì + óïðàâëåíèÿ ñèãíàëüíûì ïðîöåññîðîì, åñëè ïðîøèâêà óæå áûëà çàãðóæåíà + (íàïðèìåð, ÷åðåç JTAG èíòåðôåéñ ïðè îòëàäêå). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] mode Ðåæèì ðàáîòû ìîäóëÿ èç #t_x502_mode. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_SetMode(t_x502_hnd hnd, uint32_t mode); +/***************************************************************************//** + @brief Ïîëó÷åíèå òåêóùåãî ðåæèìà ðàáîòû ìîäóëÿ + + Ôóíêöèÿ âîçâðàùàåò òåêóùèé ðåæèì ðàáîòû ìîäóëÿ. + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] mode  äàííîì ïàðàìåòðå âîçâðàùàåòñÿ òåêóùèé ðåæèì + ðàáîòû ìîäóëÿ (èç #t_x502_mode). + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_GetMode(t_x502_hnd hnd, uint32_t* mode); +/***************************************************************************//** + @brief Óñòàíîâèòü êîýôôèöèåíòû äëÿ êàëèáðîâêè çíà÷åíèé ÀÖÏ + + Ôóíêöèÿ çàïèñûâàåò â ÏËÈÑ êîýôôèöèåíòû äëÿ êàëèáðîâêè çíà÷åíèé ÀÖÏ. + Ïðè îòêðûòèè ìîäóëÿ, áèáëèîòåêà ñ÷èòûâàåò êàëèáðîâî÷íûå êîýôôèöèåíòû èç + çàùèùåííîé îáëàñòè Flash-ïàìÿòè ìîäóëÿ è çàïèñûâàåò èõ â ÏËÈÑ äëÿ + âûïîëíåíèÿ êàëèáðîâêè íà ëåòó. + + Ðåçóëüòèðóþùåå çíà÷åíèå ÀÖÏ âû÷èñëÿåòñÿ ïî ôîðìóëå (val - offs) * k, ãäå val --- + íåêàëèáðîâàííîå çíà÷åíèå. + + Äàííàÿ ôóíêöèÿ ïîçâîëÿåò èçìåíèòü èñïîëüçóåìûå êîýôôèöèåíòû â òî âðåìÿ, ïîêà + íå çàïóùåí ñèíõðîííûé ñáîð äàííûõ. Ïðè ýòîì èçìåíÿþòñÿ òîëüêî òåêóùèå + êîýôôèöèåíòû, à çàâîäñêèå êàëèáðîâî÷íûå êîýôôèöèåíòû èç Flash-ïàìÿòè + ñîõðàíÿþò ñâîå çíà÷åíèå è ïðè ñëåäóþùåì îòêðûòèè áóäóò âîññòàíîâëåíû. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] range Äèàïàçîí ÀÖÏ (èç #t_x502_adc_range). + @param[in] k Óñòàíàâëèâàåìîå çíà÷åíèå êîýôôèöèåíòà øêàëû. + @param[in] offs Óñòàíàâëèâàåìîå çíà÷åíèå ñìåùåíèÿ íóëÿ. + @return Êîä îøèáêè. + ***************************************************************************/ +X502_EXPORT(int32_t) X502_SetAdcCoef(t_x502_hnd hnd, uint32_t range, double k, double offs); + +/***************************************************************************//** + @brief Ïîëó÷åíèå òåêóùèõ êàëèáðîâî÷íûõ êîýôôèöèåíòîâ ÀÖÏ + + Ôóíêöèÿ âîçâðàùàåò òåêóùèå êàëèáðîâî÷íûå êîýôôèöèåíòû äëÿ çàäàííîãî äèàïàçîíà + èçìåðåíèÿ ÀÖÏ. Ýòè êîýôôèöèåíòû ìîãóò îòëè÷àòüñÿ îò çàâîäñêèõ çíà÷åíèé, + ñîõðàíåííûõ âî Flash-ïàìÿòè ìîäóëÿ, íàïðèìåð, åñëè ïîëüçîâàòåëü èñïîëüçîâàë + X502_SetAdcCoef() äëÿ óñòàíîâêè ñâîèõ êîýôôèöèåíòîâ. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] range Äèàïàçîí ÀÖÏ (èç #t_x502_adc_range). + @param[in] k  äàííîé ïåðåìåííîé âîçâðàùàåòñÿ òåêóùèé êîýôôèöèåíò + øêàëû. + @param[in] offs  äàííîé ïåðåìåííîé âîçâðàùàåòñÿ òåêóùåå ñìåùåíèå íóëÿ. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_GetAdcCoef(t_x502_hnd hnd, uint32_t range, double* k, double* offs); + + + +/***************************************************************************//** + @brief Óñòàíîâèòü êîýôôèöèåíòû äëÿ êàëèáðîâêè çíà÷åíèé ÖÀÏ + + Ôóíêöèÿ óñòàíàâëèâàåò êàëèáðîâî÷íûå êîýôôèöèåíòû äëÿ çàäàííîãî êàíàëà ÀÖÏ, + êîòîðûå áóäóò èñïîëüçîâàòüñÿ ôóíêöèÿìè x502api äëÿ êàëèáðîâêè âûâîäèìûõ + çíà÷åíèé ÖÀÏ, åñëè óêàçàí ôàëàã #X502_DAC_FLAGS_CALIBR. + + Îòêàëèáðîâàííîå çíà÷åíèå ÖÀÏ â êîäàõ ïîëó÷àåòñÿ êàê + (val + offs) * k, ãäå val --- íåêàëèáðîâàííîå çíà÷åíèå (â êîäàõ). + + Ïðè îòêðûòèè ìîäóëÿ, áèáëèîòåêà ñ÷èòûâàåò êàëèáðîâî÷íûå êîýôôèöèåíòû èç + çàùèùåííîé îáëàñòè Flash-ïàìÿòè ìîäóëÿ è èñïîëüçóåò èõ. + + Äàííàÿ ôóíêöèÿ íóæíà òîëüêî åñëè ïîëüçîâàòåëü õî÷åò èñïîëüçîâàòü ñâîè + êîýôôèöèåíòû. Ïðè ýòîì îíà íå èçìåíÿåò çíà÷åíèÿ âî Flash-ïàìÿòè, ò.å. + ïðè ñëåäóþùåì îòêðûòèè ìîäóëÿ êîýôôèöèåíòû áóäóò ñíîâà âîññòàíîâëåíû èç + Flash-ïàìÿòè. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] ch Êàíàë ÖÀÏ (èç #t_x502_dac_ch). + @param[in] k Óñòàíàâëèâàåìîå çíà÷åíèå êîýôôèöèåíòà øêàëû. + @param[in] offs Óñòàíàâëèâàåìîå çíà÷åíèå ñìåùåíèÿ íóëÿ. + @return Êîä îøèáêè. + ***************************************************************************/ +X502_EXPORT(int32_t) X502_SetDacCoef(t_x502_hnd hnd, uint32_t ch, double k, double offs); + + +/***************************************************************************//** + @brief Ïîëó÷åíèå òåêóùèõ êàëèáðîâî÷íûõ êîýôôèöèåíòîâ ÖÀÏ + + Ôóíêöèÿ âîçâðàùàåò òåêóùèå êàëèáðîâî÷íûå êîýôôèöèåíòû äëÿ çàäàííîãî êàíàëà ÖÀÏ. + Ýòè êîýôôèöèåíòû ìîãóò îòëè÷àòüñÿ îò çàâîäñêèõ çíà÷åíèé, + ñîõðàíåííûõ âî Flash-ïàìÿòè ìîäóëÿ, íàïðèìåð, åñëè ïîëüçîâàòåëü èñïîëüçîâàë + X502_SetDacCoef() äëÿ óñòàíîâêè ñâîèõ êîýôôèöèåíòîâ. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] ch Êàíàë ÖÀÏ (èç #t_x502_dac_ch). + @param[in] k  äàííîé ïåðåìåííîé âîçâðàùàåòñÿ òåêóùèé êîýôôèöèåíò + øêàëû. + @param[in] offs  äàííîé ïåðåìåííîé âîçâðàùàåòñÿ òåêóùåå ñìåùåíèå íóëÿ. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_GetDacCoef(t_x502_hnd hnd, uint32_t ch, double* k, double* offs); + + +/***************************************************************************//** + @brief Ðàññ÷åò ÷àñòîòû ñáîðà ÀÖÏ + + Èñõîäÿ èç çàäàííûõ ïàðàìåòðîâ, ôóíêöèÿ ïîäáèðàåò äåëèòåëü ÷àñòîòû + ÀÖÏ è çíà÷åíèå ìåæêàäðîâîé çàäåðæêè òàê, ÷òîáû ïîëó÷åííûå ÷àñòîòû áûëè + íàèáîëåå áëèçêè ê çàäàííûì, è âîçâðàùàåò ïîëó÷åííûå çíà÷åíèÿ ÷àñòîò. + +  îòëè÷èå îò X502_SetAdcFreq(), äàííàÿ ôóíêöèÿ ïðåäíàçíà÷åíà ïîëó÷åíèÿ + ñêîððåêòèðîâàííîé ÷àñòîòû áåç èñïîëüçîâàíèÿ îïèñàòåëÿ ìîäóëÿ è + òîëüêî ðàññ÷èòûâàåò ðåçóëüòèðóþùèå ïàðàìåòðû, íå èçìåíÿÿ íàñòðîéêè. + + Äëÿ ìîäóëÿ E16 â ïàðàìåòðå ref_freq ñëåäóåò ïåðåäàâàòü E16_REF_FREQ_48000KHZ, + ëèáî èñïîëüçîâàòü ôóíêöèþ X502_CalcAdcFreq2! + + + @param[in] ref_freq Çíà÷åíèå îïîðíîé ÷àñòîòû â Ãö (âíåøíåé èëè âíóòðåííåé) + @param[in] lch_cnt Êîëè÷åñòâî ëîãè÷åñêèõ êàíàëîâ, êîòîðîå áóäåò èñïîëüçîâàíî. + Íåîáõîäèìî äëÿ ðàñ÷åòà ìåæêàäðîâîé çàäåðæêè. Åñëè + â êà÷åñòâå f_frame ïåðåäàí íóëåâîé óêàçàòåëü, òî + ìîæåò áûòü ðàâíî 0. + @param[in,out] f_acq Íà âõîäå ïðèíèìàåò òðåáóåìîå çíà÷åíèÿ ÷àñòîòû ñáîðà + ÀÖÏ â Ãåðöàõ. Íà âûõîäå âîçâðàùàåò ðàññ÷èòàííîå + çíà÷åíèå ÷àñòîòû, êîòîðàÿ ìîæåò áûòü óñòàíîâëåíà. + @param[in,out] f_frame Íà âõîäå ïðèíèìàåò òðåáóåìîå çíà÷åíèå ÷àñòîòû ñáîðà + êàäðîâ (÷àñòîòû ñáîðà íà ëîãè÷åñêèé êàíàë) ÀÖÏ + â Ãåðöàõ. Íà âûõîäå âîçâðàùàåò ðàññ÷èòàííîå çíà÷åíèå. + Åñëè ïåðåäàí íóëåâîé óêàçàòåëü, òî çàäåðæêà + ðàññ÷èòàíà íå áóäåò. + Åñëè ïåðåäàíî çíà÷åíèå ìåíüøå èëè ðàâíîå íóëÿ, òî + áóäåò ðàññ÷èòàíà ìàêñèìàëüíàÿ ÷àñòîòà êàäðîâ + (ñ íóëåâîé ìåæêàäðîâîé çàäåðæêîé). + @param[out] result_freq_div  äàííîì ïàðàìåòðå âîçâðàùàåòñÿ ðàññ÷èòàííîå + çíà÷åíèÿ äåëèòåëÿ ÷àñòîòû ÀÖÏ. Ìîæåò áûòü ïåðåäàí + íóëåâîé óêàçàòåëü, åñëè ýòî çíà÷åíèå ÿâíî çíàòü + íå òðåáóåòñÿ. + @param[out] result_frame_delay  äàííîì ïàðàìåòðå âîçâðàùàåòñÿ ðàññ÷èòàííîå + çíà÷åíèå ìåæêàäðîâîé çàäåðæêè. Ìîæåò áûòü ïåðåäàí + íóëåâîé óêàçàòåëü, åñëè ýòî çíà÷åíèå ÿâíî çíàòü + íå òðåáóåòñÿ. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_CalcAdcFreq(double ref_freq, uint32_t lch_cnt, double *f_acq, + double *f_frame, uint32_t *result_freq_div, uint32_t *result_frame_delay); + + /***************************************************************************//** + @brief Ðàññ÷åò ÷àñòîòû ñáîðà ÀÖÏ + + Èñõîäÿ èç çàäàííûõ ïàðàìåòðîâ, ôóíêöèÿ ïîäáèðàåò äåëèòåëü ÷àñòîòû + ÀÖÏ è çíà÷åíèå ìåæêàäðîâîé çàäåðæêè òàê, ÷òîáû ïîëó÷åííûå ÷àñòîòû áûëè + íàèáîëåå áëèçêè ê çàäàííûì, è âîçâðàùàåò ïîëó÷åííûå çíà÷åíèÿ ÷àñòîò. + +  îòëè÷èå îò X502_SetAdcFreq(), äàííàÿ ôóíêöèÿ ïðåäíàçíà÷åíà ïîëó÷åíèÿ + ñêîððåêòèðîâàííîé ÷àñòîòû è òîëüêî ðàññ÷èòûâàåò ðåçóëüòèðóþùèå ïàðàìåòðû, íå èçìåíÿÿ íàñòðîéêè. + Îïèñàòåëü ìîäóëÿ â ýòîé ôóíêöèè íóæåí äëÿ òîãî ÷òîáû ðàçëè÷àòü ìîäóëè E16 è X502. + + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] ref_freq Çíà÷åíèå îïîðíîé ÷àñòîòû â Ãö (âíåøíåé èëè âíóòðåííåé) + @param[in] lch_cnt Êîëè÷åñòâî ëîãè÷åñêèõ êàíàëîâ, êîòîðîå áóäåò èñïîëüçîâàíî. + Íåîáõîäèìî äëÿ ðàñ÷åòà ìåæêàäðîâîé çàäåðæêè. Åñëè + â êà÷åñòâå f_frame ïåðåäàí íóëåâîé óêàçàòåëü, òî + ìîæåò áûòü ðàâíî 0. + @param[in,out] f_acq Íà âõîäå ïðèíèìàåò òðåáóåìîå çíà÷åíèÿ ÷àñòîòû ñáîðà + ÀÖÏ â Ãåðöàõ. Íà âûõîäå âîçâðàùàåò ðàññ÷èòàííîå + çíà÷åíèå ÷àñòîòû, êîòîðàÿ ìîæåò áûòü óñòàíîâëåíà. + @param[in,out] f_frame Íà âõîäå ïðèíèìàåò òðåáóåìîå çíà÷åíèå ÷àñòîòû ñáîðà + êàäðîâ (÷àñòîòû ñáîðà íà ëîãè÷åñêèé êàíàë) ÀÖÏ + â Ãåðöàõ. Íà âûõîäå âîçâðàùàåò ðàññ÷èòàííîå çíà÷åíèå. + Åñëè ïåðåäàí íóëåâîé óêàçàòåëü, òî çàäåðæêà + ðàññ÷èòàíà íå áóäåò. + Åñëè ïåðåäàíî çíà÷åíèå ìåíüøå èëè ðàâíîå íóëÿ, òî + áóäåò ðàññ÷èòàíà ìàêñèìàëüíàÿ ÷àñòîòà êàäðîâ + (ñ íóëåâîé ìåæêàäðîâîé çàäåðæêîé). + @param[out] result_freq_div  äàííîì ïàðàìåòðå âîçâðàùàåòñÿ ðàññ÷èòàííîå + çíà÷åíèÿ äåëèòåëÿ ÷àñòîòû ÀÖÏ. Ìîæåò áûòü ïåðåäàí + íóëåâîé óêàçàòåëü, åñëè ýòî çíà÷åíèå ÿâíî çíàòü + íå òðåáóåòñÿ. + @param[out] result_frame_delay  äàííîì ïàðàìåòðå âîçâðàùàåòñÿ ðàññ÷èòàííîå + çíà÷åíèå ìåæêàäðîâîé çàäåðæêè. Ìîæåò áûòü ïåðåäàí + íóëåâîé óêàçàòåëü, åñëè ýòî çíà÷åíèå ÿâíî çíàòü + íå òðåáóåòñÿ. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_CalcAdcFreq2(t_x502_hnd hnd, double ref_freq, uint32_t lch_cnt, double *f_acq, + double *f_frame, uint32_t *adc_freq_div, uint32_t *adc_frame_delay); + +/***************************************************************************//** + @brief Ðàññ÷åò ÷àñòîòû ñèíõðîííîãî ââîäà ñ öèôðîâûõ âõîäîâ + + Èñõîäÿ èç çàäàííûõ ïàðàìåòðîâ, ôóíêöèÿ ïîäáèðàåò äåëèòåëü ÷àñòîòû ââîäà çíà÷åíèé + ñ öèôðîâûõ âõîäîâ òàê, ÷òîáû ïîëó÷åííàÿ ÷àñòîòà ââîäà áûëà íàèáîëåå áëèçêà ê óêàçàííîé, + è âîçâðàùàåò ïîëó÷åííîå çíà÷åíèå ÷àñòîòû. + +  îòëè÷èå îò X502_SetDinFreq(), äàííàÿ ôóíêöèÿ ïðåäíàçíà÷åíà ïîëó÷åíèÿ + ñêîððåêòèðîâàííîé ÷àñòîòû áåç èñïîëüçîâàíèÿ îïèñàòåëÿ ìîäóëÿ è + òîëüêî ðàññ÷èòûâàåò ðåçóëüòèðóþùèå ïàðàìåòðû, íå èçìåíÿÿ íàñòðîéêè. + + Äëÿ ìîäóëÿ E16 â ïàðàìåòðå ref_freq ñëåäóåò ïåðåäàâàòü E16_REF_FREQ_48000KHZ, + ëèáî èñïîëüçîâàòü ôóíêöèþ X502_CalcDinFreq2! + + @param[in] ref_freq Çíà÷åíèå îïîðíîé ÷àñòîòû â Ãö (âíåøíåé èëè âíóòðåííåé) + @param[in,out] f_din Íà âõîäå ïðèíèìàåò òðåáóåìîå çíà÷åíèÿ ÷àñòîòû ââîäà + ñ öèôðîâûõ âõîäîâ â Ãåðöàõ. Íà âûõîäå âîçâðàùàåò + ðàññ÷èòàííîå çíà÷åíèå ÷àñòîòû, êîòîðîå ìîæåò áûòü + óñòàíîâëåíî. + @param[out] result_freq_div  äàííîì ïàðàìåòðå âîçâðàùàåòñÿ ðàññ÷èòàííîå + çíà÷åíèÿ äåëèòåëÿ ÷àñòîòû ñèíõðîííîãî ââîäà öèôðîâûõ + ëèíèé. + Ìîæåò áûòü ïåðåäàí íóëåâîé óêàçàòåëü, + åñëè ýòî çíà÷åíèå ÿâíî çíàòü íå òðåáóåòñÿ. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_CalcDinFreq(double ref_freq, double *f_din, uint32_t *result_freq_div); + +/***************************************************************************//** + @brief Ðàññ÷åò ÷àñòîòû ñèíõðîííîãî ââîäà ñ öèôðîâûõ âõîäîâ + + Èñõîäÿ èç çàäàííûõ ïàðàìåòðîâ, ôóíêöèÿ ïîäáèðàåò äåëèòåëü ÷àñòîòû ââîäà çíà÷åíèé + ñ öèôðîâûõ âõîäîâ òàê, ÷òîáû ïîëó÷åííàÿ ÷àñòîòà ââîäà áûëà íàèáîëåå áëèçêà ê óêàçàííîé, + è âîçâðàùàåò ïîëó÷åííîå çíà÷åíèå ÷àñòîòû. + +  îòëè÷èå îò X502_SetDinFreq(), äàííàÿ ôóíêöèÿ òîëüêî ðàññ÷èòûâàåò ðåçóëüòèðóþùèå ïàðàìåòðû, íå èçìåíÿÿ íàñòðîéêè. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] ref_freq Çíà÷åíèå îïîðíîé ÷àñòîòû â Ãö (âíåøíåé èëè âíóòðåííåé) + @param[in,out] f_din Íà âõîäå ïðèíèìàåò òðåáóåìîå çíà÷åíèÿ ÷àñòîòû ââîäà + ñ öèôðîâûõ âõîäîâ â Ãåðöàõ. Íà âûõîäå âîçâðàùàåò + ðàññ÷èòàííîå çíà÷åíèå ÷àñòîòû, êîòîðîå ìîæåò áûòü + óñòàíîâëåíî. + @param[out] result_freq_div  äàííîì ïàðàìåòðå âîçâðàùàåòñÿ ðàññ÷èòàííîå + çíà÷åíèÿ äåëèòåëÿ ÷àñòîòû ñèíõðîííîãî ââîäà öèôðîâûõ + ëèíèé. + Ìîæåò áûòü ïåðåäàí íóëåâîé óêàçàòåëü, + åñëè ýòî çíà÷åíèå ÿâíî çíàòü íå òðåáóåòñÿ. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_CalcDinFreq2(t_x502_hnd hnd, double ref_freq, double *f_din, uint32_t *result_freq_div); + +/***************************************************************************//** + @brief Ðàññ÷åò ÷àñòîòû ñèíõðîííîãî âûâîäà + + Èñõîäÿ èç çàäàííûõ ïàðàìåòðîâ, ôóíêöèÿ ïîäáèðàåò äåëèòåëü ÷àñòîòû ñèíõðîííîãî + âûâîäà òàê, ÷òîáû ïîëó÷åííàÿ ÷àñòîòà áûëà íàèáîëåå áëèçêà ê óêàçàííîé, + è âîçâðàùàåò ïîëó÷åííîå çíà÷åíèå ÷àñòîòû. + +  îòëè÷èå îò X502_SetOutFreq(), äàííàÿ ôóíêöèÿ ïðåäíàçíà÷åíà ïîëó÷åíèÿ + ñêîððåêòèðîâàííîé ÷àñòîòû áåç èñïîëüçîâàíèÿ îïèñàòåëÿ ìîäóëÿ è + òîëüêî ðàññ÷èòûâàåò ðåçóëüòèðóþùèå ïàðàìåòðû, íå èçìåíÿÿ íàñòðîéêè. + + Ôóíêöèÿ ïðåäïîëàãàåò, ÷òî ìîäóëü ïîääåðæèâàåò èçìåíåíèå ÷àñòîòû âûâîäà + (ñì. òðåáîâàíèÿ ê ìîäóëþ äëÿ ýòîãî â îïèñàíèè ôóíêöèè X502_SetOutFreq()). + + Äëÿ ìîäóëÿ E16 â ïàðàìåòðå ref_freq ñëåäóåò ïåðåäàâàòü E16_REF_FREQ_48000KHZ, + ëèáî èñïîëüçîâàòü ôóíêöèþ X502_CalcOutFreq2! + + @param[in] ref_freq Çíà÷åíèå îïîðíîé ÷àñòîòû â Ãö (âíåøíåé èëè âíóòðåííåé) + @param[in,out] f_dout Íà âõîäå ïðèíèìàåò òðåáóåìîå çíà÷åíèÿ ÷àñòîòû + ñèíõðîííîãî âûâîäà â Ãåðöàõ. Íà âûõîäå âîçâðàùàåò + ðàññ÷èòàííîå çíà÷åíèå ÷àñòîòû, êîòîðîå ìîæåò áûòü + óñòàíîâëåíî. + @param[out] result_freq_div  äàííîì ïàðàìåòðå âîçâðàùàåòñÿ ðàññ÷èòàííîå + çíà÷åíèÿ äåëèòåëÿ ÷àñòîòû ñèíõðîííîãî âûâîäà. + Ìîæåò áûòü ïåðåäàí íóëåâîé óêàçàòåëü, + åñëè ýòî çíà÷åíèå ÿâíî çíàòü íå òðåáóåòñÿ. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_CalcOutFreq(double ref_freq, double *f_dout, uint32_t *result_freq_div); + +/***************************************************************************//** + @brief Ðàññ÷åò ÷àñòîòû ñèíõðîííîãî âûâîäà + + Èñõîäÿ èç çàäàííûõ ïàðàìåòðîâ, ôóíêöèÿ ïîäáèðàåò äåëèòåëü ÷àñòîòû ñèíõðîííîãî + âûâîäà òàê, ÷òîáû ïîëó÷åííàÿ ÷àñòîòà áûëà íàèáîëåå áëèçêà ê óêàçàííîé, + è âîçâðàùàåò ïîëó÷åííîå çíà÷åíèå ÷àñòîòû. + +  îòëè÷èå îò X502_SetOutFreq(), äàííàÿ ôóíêöèÿ ïðåäíàçíà÷åíà ïîëó÷åíèÿ + ñêîððåêòèðîâàííîé ÷àñòîòû áåç èñïîëüçîâàíèÿ îïèñàòåëÿ ìîäóëÿ è + òîëüêî ðàññ÷èòûâàåò ðåçóëüòèðóþùèå ïàðàìåòðû, íå èçìåíÿÿ íàñòðîéêè. + + Ôóíêöèÿ ïðåäïîëàãàåò, ÷òî ìîäóëü ïîääåðæèâàåò èçìåíåíèå ÷àñòîòû âûâîäà + (ñì. òðåáîâàíèÿ ê ìîäóëþ äëÿ ýòîãî â îïèñàíèè ôóíêöèè X502_SetOutFreq()). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] ref_freq Çíà÷åíèå îïîðíîé ÷àñòîòû â Ãö (âíåøíåé èëè âíóòðåííåé) + @param[in,out] f_dout Íà âõîäå ïðèíèìàåò òðåáóåìîå çíà÷åíèÿ ÷àñòîòû + ñèíõðîííîãî âûâîäà â Ãåðöàõ. Íà âûõîäå âîçâðàùàåò + ðàññ÷èòàííîå çíà÷åíèå ÷àñòîòû, êîòîðîå ìîæåò áûòü + óñòàíîâëåíî. + @param[out] result_freq_div  äàííîì ïàðàìåòðå âîçâðàùàåòñÿ ðàññ÷èòàííîå + çíà÷åíèÿ äåëèòåëÿ ÷àñòîòû ñèíõðîííîãî âûâîäà. + Ìîæåò áûòü ïåðåäàí íóëåâîé óêàçàòåëü, + åñëè ýòî çíà÷åíèå ÿâíî çíàòü íå òðåáóåòñÿ. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_CalcOutFreq2(t_x502_hnd hnd, double ref_freq, double *f_dout, uint32_t *result_freq_div); + +/** @} */ + + +/***************************************************************************//** + @addtogroup func_async Ôóíêöèè àñèíõðîííîãî ââîäà-âûâîäà + @{ +*******************************************************************************/ + +/***************************************************************************//** + @brief Àñèíõðîííûé âûâîä äàííûõ íà êàíàë ÖÀÏ + + Ôóíêöèÿ âûâîäèò óêàçàííîå çíà÷åíèå íà óêàçàííûé êàíàë ÖÀÏ. Çíà÷åíèå ìîæåò + áûòü çàäàíî êàê â êîäàõ, òàê è â Âîëüòàõ, è ê íåìó ìîãóò áûòü ïðèìåíåíû + êàëèáðîâî÷íûå êîýôôèöèåíòû (îïðåäåëÿåòñÿ ôëàãàìè). + + Ôóíêöèÿ ìîæåò âûçûâàòüñÿ ëèáî êîãäà ñèíõðîííûé ñáîð íå çàïóùåí, ëèáî ïðè + çàïóùåííîì ñáîðå äàííûõ, åñëè ñèíõðîííûé âûâîä ïî ýòîìó êàíàëó ÖÀÏ + íå ðàçðåøåí. + + @note Ôóíêöèÿ íå ðàáîòàåò â ñëó÷àå, åñëè ìîäóëü íàõîäèòñÿ â îæèäàíèè âíåøíåãî + óñëîâèÿ äëÿ çàïóñêà ñèíõðîííîãî ââîäà (áûëà âûçâàíà X502_StreamsStart() ïðè + âíåøíåé ñèíõðîíèçàöèè ñòàðòà, íî ñàì ñèãíàë çàïóñêà åùå íå âîçíèê). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] ch Íîìåð êàíàëà ÖÀÏ (èç #t_x502_dac_ch). + @param[in] data Âûâîäèìîå çíà÷åíèå íà ÖÀÏ (â êîäàõ èëè âîëüòàõ) + @param[in] flags Ôëàãè èç #t_x502_dacout_flags. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_AsyncOutDac(t_x502_hnd hnd, uint32_t ch, double data, uint32_t flags); + +/***************************************************************************//** + @brief Àñèíõðîííûé âûâîä äàííûõ íà êàíàë ÖÀÏ + + Ôóíêöèÿ âûâîäèò óêàçàííîå çíà÷åíèå íà óêàçàííûé êàíàë ÖÀÏ. Çíà÷åíèå ìîæåò + áûòü çàäàíî êàê â êîäàõ, òàê è â Âîëüòàõ, è ê íåìó ìîãóò áûòü ïðèìåíåíû + êàëèáðîâî÷íûå êîýôôèöèåíòû (îïðåäåëÿåòñÿ ôëàãàìè). + + Ôóíêöèÿ ìîæåò âûçûâàòüñÿ ëèáî êîãäà ñèíõðîííûé ñáîð íå çàïóùåí, ëèáî ïðè + çàïóùåííîì ñáîðå äàííûõ, åñëè ñèíõðîííûé âûâîä ïî ýòîìó êàíàëó ÖÀÏ + íå ðàçðåøåí. + + @note Ôóíêöèÿ íå ðàáîòàåò â ñëó÷àå, åñëè ìîäóëü íàõîäèòñÿ â îæèäàíèè âíåøíåãî + óñëîâèÿ äëÿ çàïóñêà ñèíõðîííîãî ââîäà (áûëà âûçâàíà X502_StreamsStart() ïðè + âíåøíåé ñèíõðîíèçàöèè ñòàðòà, íî ñàì ñèãíàë çàïóñêà åùå íå âîçíèê). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] range Äèàïàçîí èëè âûõîäû îäíîãî êàíàëà (äëÿ LTA37 1:1 èëè 1:10) (èç #t_x502devs_dac_range) + @param[in] ch Íîìåð êàíàëà ÖÀÏ (èç #t_x502_dac_ch). + @param[in] data Âûâîäèìîå çíà÷åíèå íà ÖÀÏ (â êîäàõ èëè âîëüòàõ) + @param[in] flags Ôëàãè èç #t_x502_dacout_flags. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_AsyncOutDac2(t_x502_hnd hnd, uint32_t range, uint32_t ch, double data, uint32_t flags); + +/***************************************************************************//** + @brief Àñèíõðîííûé âûâîä äàííûõ íà öèôðîâûå âûõîäû + + Ôóíêöèÿ âûâîäèò óêàçàííîå çíà÷åíèå íà öèôðîâûå âûõîäû ìîäóëÿ. + Ôîðìàò çíà÷åíèÿ àíàëîãè÷åí X502_PrepareData() - â ìëàäøèõ 16 áèòàõ + óêàçûâàåòñÿ âûâîäèìîå çíà÷åíèå, à â ñòàðøèå - ôëàãè (ñ ïîìîùüþ êîòîðûõ ìîæíî + ïåðåâåñòè îäíó èç ïîëîâèí â òðåòüå ñîñòîÿíèå). + + Ôóíêöèÿ ìîæåò âûçûâàòüñÿ ëèáî êîãäà ñèíõðîííûé ñáîð íå çàïóùåí, ëèáî ïðè + çàïóùåííîì ñáîðå äàííûõ, åñëè ñèíõðîííûé âûâîä ïî öèôðîâûì ëèíèÿì íå ðàçðåøåí. + + Ìîæíî èñïîëüçîâàòü ìàñêó, ÷òîáû âûâåñòè òîëüêî íà ÷àñòü âûâîäîâ, îñòàâèâ + îñòàëüíûå íåèçìåííûìè, îäíàêî ñëåäóåò ó÷åñòü, ÷òî ïîñëå îòêðûòèÿ ñâÿçè ñ + ìîäóëåì íåîáõîäèìî ñïåðâà ñäåëàòü âûâîä íà âñå ëèíèè, ïîñëå ÷åãî óæå + ìîæíî èñïîëüçîâàòü ìàñêó ïðè ïîñëåäóþùèõ âûçîâàõ. + + @note Ôóíêöèÿ íå ðàáîòàåò â ñëó÷àå, åñëè ìîäóëü íàõîäèòñÿ â îæèäàíèè âíåøíåãî + óñëîâèÿ äëÿ çàïóñêà ñèíõðîííîãî ââîäà (áûëà âûçâàíà X502_StreamsStart() ïðè + âíåøíåé ñèíõðîíèçàöèè ñòàðòà, íî ñàì ñèãíàë çàïóñêà åùå íå âîçíèê). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] val Ìëàäøàÿ ïîëîâèíà - âûâîäèìîå çíà÷åíèå, ñòàðøàÿ - + ôëàãè èç #t_x502_digout_word_flags. + Áèòû 0..15 ñîîòâåòñòâóþò âûõîäàì OUT1..OUT16 (E16) + DO1..DO16 (x502) íà ñèãíàëüíîì ðàçú¸ìå + @param[in] msk Ìàñêà - óêàçàííûå â ìàñêå áèòû íå áóäóò èçìåíÿòüñÿ + ñ ïðåäûäóùåãî âûâåäåííîãî ñîñòîÿíèÿ (ðàñïðîñòðàíÿåòñÿ + è íà ñòàðøóþ ïîëîâèíó val). + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_AsyncOutDig(t_x502_hnd hnd, uint32_t val, uint32_t msk); + + +/***************************************************************************//** + @brief Àñèíõðîííûé ââîä çíà÷åíèé ñ öèôðîâûõ âõîäîâ + + Ôóíêöèÿ ñ÷èòûâàåò òåêóùåå çíà÷åíèå öèôðîâûõ âõîäîâ. + Ïðè ýòîì ñèíõðîííûé ñáîð öèôðîâûõ âõîäîâ íå äîëæåí áûòü çàïóùåí (íå ðàçðåøåí + ïîòîê #X502_STREAM_DIN). + + Òàê êàê ìîäóëè E-502/L-502 íå ïîääåðæèâàþò àïïàðàòíî àñèíõðîííûé ââîä, òî åñëè íà + ìîìåíò âûçîâà ýòîé ôóíêöèè íå çàïóùåí ñèíõðîííûé ââîä/âûâîä ñ ïîìîùüþ + X502_StreamsStart(), òî äàííàÿ ôóíêöèÿ íà âðåìÿ âûïîëíåíèÿ çàïóñêàåò + ñèíõðîííûé ñáîð è îñòàíàâëèâàåò åãî êàê òîëüêî áóäåò ïîëó÷åíî îäíî íîâîå + çíà÷åíèå öèôðîâûõ âõîäîâ, äëÿ ìîäóëåé E16 ýòîãî íå òðåáóåòñÿ. + + Äëÿ ìîäóëÿ E16 17 áèò - ñîñòîÿíèå âõîäà INT, 18 áèò - TRIG + + @note Ôóíêöèÿ íå ðàáîòàåò â ñëó÷àå, åñëè ìîäóëü íàõîäèòñÿ â îæèäàíèè âíåøíåãî + óñëîâèÿ äëÿ çàïóñêà ñèíõðîííîãî ââîäà (áûëà âûçâàíà X502_StreamsStart() ïðè + âíåøíåé ñèíõðîíèçàöèè ñòàðòà, íî ñàì ñèãíàë çàïóñêà åùå íå âîçíèê). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] din Ïðè óñïåøíîì âûïîëíåíèè â ýòîé ïåðåìåííîé + âîçâðàùàåòñÿ òåêóùåå ñîñòîÿíèå öèôðîâûõ âõîäîâ. + Äåéñòâèòåëüíû ìëàäøèå 18 áèò, ñòàðøèå 14 - ðåçåðâ. + ×àñòü áèòîâ ïðè ýòîì îáúåäèíåíû ñ ëèíèÿìè ñèíõðîíèçàöèè, + ïðè ýòîì ýòî îáúåäèíåíèå çàâèñèò îò òèïà ìîäóëÿ. + Ïîäðîáíåå îïèñàíî â ðàçäåëå ñ ðàçëè÷èÿìè ìîäóëåé + E-502 è L-502. + Ðåçåðâíûå áèòû ìîãóò áûòü èñïîëüçîâàíû â ïîñëåäóþùèõ + âåðñèÿõ, íå ñëåäóåò ñ÷èòàòü, ÷òî îíè âñåãäà áóäóò + ðàâíû íóëþ! + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_AsyncInDig(t_x502_hnd hnd, uint32_t* din); + + +/***************************************************************************//** + @brief Àñèíõðîííûé ââîä îäíîãî êàäðà ÀÖÏ + + Ôóíêèÿ ïðîèçâîäèò îäíîêðàòíûé ââîä êàäðà â ñîîòâåòñòâèè ñ çàðàíåå + óñòàíîâëåííîé ëîãè÷åñêîé òàáëèöåé. ×àñòîòà ñáîðà ÀÖÏ ñîîòâåòñòâóåò ÷àñòîòå, + óñòàíîâëåííîé ñ ïîìîùüþ X502_SetAdcFreq(). ×àñòîòà ñëåäîâàíèÿ êàäðîâ + çíà÷åíèÿ íå èìååò. Ñàì êàäð ââîäèòñÿ ñèíõðîííî, íî ïðè ïîñëåäîâàòåëüíîì + âûçîâå X502_AsyncGetAdcFrame() äëÿ èçìåðåíèÿ íåñêîëüêèõ êàäðîâ çàäåðæêà + ìåæäó ýòèìè êàäðàìè íå îïðåäåëåíà. + + Ôóíêöèÿ òàê æå âûïîëíÿåò îáðàáîòêó ïðèíÿòûõ äàííûõ ÀÖÏ, àíàëîãè÷íî + X502_ProcessAdcData(), è ïðèíèìàåò íàáîð ôëàãîâ, àíàëîãè÷íûé + X502_ProcessAdcData(). + + Äëÿ ðàáîòû ýòîé ôóíêöèè íå äîëæåí áûòü ðàçðåøåí ñèíõðîííûé ââîä ÀÖÏ + è öèôðîâûõ ëèíèé. + + Òàê êàê àïïàðàòíî àñèíõðîííûé ââîä â ïëàòå îòñóòñòâóåò, òî ýòà ôóíêöèÿ + â ñëó÷àå íå çàïóùåííîãî ïîòîêà çàïóñêàåò åãî âíóòðè ñåáÿ, ïðèíèìàåò îäèí + êàäð äàííûõ è ïîñëå ýòîãî îñòàíàâëèâàåò ñèíõðîííûé ñáîð. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] flags Ôëàãè èç t_x502_proc_flags + @param[in] tout Òàéìàóò íà âûïîëíåíèå ôóíêöèè â ìñ + @param[out] data Ìàññèâ, â êîòîðîì â ñëó÷àå óñïåõà áóäóò âîçâðàùåíû + îòñ÷åòû êàäðà ÀÖÏ. Äîëæåí áûòü ðàçìåðîì, äîñòàòî÷íûì + äëÿ õðàíåíèÿ îòñ÷åòîâ òèïà double â êîëè÷åñòâå, + ðàâíîì êîëè÷åñòâó óñòàíîâëåííûõ ëîãè÷åñêèõ êàíàëîâ + â óïðàâëÿþùåé òàáëèöå ÀÖÏ. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_AsyncGetAdcFrame(t_x502_hnd hnd, uint32_t flags, + uint32_t tout, double* data); + +/** @} */ + + + + + +/***************************************************************************//** + @addtogroup func_streams Ôóíêöèè äëÿ ðàáîòû ñ ñèíõðîííûì ïîòîêîâûì ââîäîì-âûâîäîì + @{ +*******************************************************************************/ + +/***************************************************************************//** + @brief Ðàçðåøåíèå ñèíõðîííûõ ïîòîêîâ íà ââîä/âûâîä + + Ôóíêöèÿ ðàçðåøàåò ïðèåì/ïåðåäà÷ó äëÿ óêàçàííûõ ïîòîêîâ + Íå óêàçàííûå ïîòîêè ñîõðàíÿþò ñâîå ðàçðåøåííîå èëè çàïðåùåííîå ñîñòîÿíèå. + Ìîæåò âûçûâàòüñÿ êàê äî X502_Configure(), òàê è ïîñëå. + Ðàçðåøåííûå ïîòîêè óñòàíàâëèâàþòñÿ êàê ïðàâèëî äî âûçîâà X502_StreamsStart(). + + Ïðè æåëàíèè â íåêîòîðûõ ñèòóàöèÿõ ìîæíî èçìåíÿòü ñîñòàâ ðàçðåøåííûõ ïîòîêîâ + âî âðåìÿ çàïóùåííîãî ñáîðà äàííûõ, îäíàêî åñëè ýòè ïîòîêè ñèëüíî ðàçëè÷àþòñÿ + â ÷àñòîòå, òî ðàññ÷èòàííûå áèáëèîòåêîé çíà÷åíèÿ áóôåðà è øàãà ïðåðûâàíèÿ + ìîãóò íå ïîäõîäèòü äëÿ èçìåíèâøèõñÿ çíà÷åíèé (ñì. @ref sect_sync_mode_buf) + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] streams Íàáîð ôëàãîâ #t_x502_streams, óêàçûâàþùèõ, êàêèå ïîòîêè + äîëæíû áûòü ðàçðåøåíû. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_StreamsEnable(t_x502_hnd hnd, uint32_t streams); +/****************************************************************************//** + @brief Çàïðåùåíèå ñèíõðîííûõ ïîòîêîâ íà ââîä/âûâîä + + Ôóíêöèÿ çàïðåùàåò ïåðåäà÷ó ñèíõðîííûõ äàííûõ äëÿ óêàçàííûõ ïîòîêîâ. + Íå óêàçàííûå ïîòîêè ñîõðàíÿþò ñâîå ðàçðåøåííîå èëè çàïðåùåííîå ñîñòîÿíèå. + Ôóíêöèÿ, ïðîòèâîïîëîæíàÿ ïî ñìûñëó X502_StreamsEnable(). + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] streams Íàáîð ôëàãîâ #t_x502_streams, óêàçûâàþùèõ, êàêèå ïîòîêè + äîëæíû áûòü çàïðåùåíû. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_StreamsDisable(t_x502_hnd hnd, uint32_t streams); + +/****************************************************************************//** + @brief Ïîëó÷èòü çíà÷åíèå, êàêèå ñèíõðîííûå ïîòîêè ðàçðåøåíû + + Ôóíêöèÿ ïîçâîëÿåò ïîëó÷èòü íàáîð ôëàãîâ, êîòîðûå óêàçûâàþò, êàêèå ñèíõðîííûå + ïîòîêè ñåé÷àñ ðàçðåøåíû. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] streams Íàáîð ôëàãîâ #t_x502_streams, óêàçûâàþùèõ, êàêèå ïîòîêè + ñåé÷àñ ðàçðåøåíû. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_GetEnabledStreams(t_x502_hnd hnd, uint32_t* streams); + +/***************************************************************************//** + @brief Çàïóñê ñèíõðîííûõ ïîòîêîâ ââîäà/âûâîäà + + Ôóíêöèÿ çàïóñêà ñèíõðîííûõ ïîòîêîâ äàííûõ. Âñå ñèíõðîííûå ïîòîêè òàêòèðóþòñÿ + îò îáùåé îïîðíîé ÷àñòîòû. Åñëè áûë óñòàíîâëåí âíóòðåííèé ñòàðò ñèíõðîíèçàöèè, + òî ñèíõðîíèçàöèÿ ïîòîêîâ íà÷íåòñÿ ïðè âûïîëíåíèè äàííîé ôóíêöèè, â ïðîòèâíîì + ñëó÷àå ïî äàííîé ôóíêöèè ìîäóëü ïåðåéäåò â ñîñòîÿíèå îæèäàíèÿ âíåøíåãî + ïðèçíàêà íà÷àëüíîé ñèíõðîíèçàöèè. + + Òàêæå ôóíêöèÿ îñóùåñòâëÿåò èíèöèàëèçàöèþ êàíàëà DMA íà ââîä äàííûõ èç ïëàòû, + åñëè áûë ðàçðåøåí ïîòîê ÀÖÏ èëè ñèíõðîííîãî ââîäà öèôðîâûõ ëèíèé, + è èíèöèàëèçàöèþ êàíàëà DMA íà âûâîä, åñëè áûë ðàçðåøåí õîòÿ áû îäèí ïîòîê íà + âûâîä, íî íå áûëà âûçâàíà ôóíêöèÿ X502_PreloadStart() (îäíàêî â ýòîì ñëó÷àå + íà÷àëî âûâîäà íå ñîâïàäåò ñ íà÷àëîì ââîäà). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_StreamsStart(t_x502_hnd hnd); + +/***************************************************************************//** + @brief Îñòàíîâ ñèíõðîííûõ ïîòîêîâ ââîäà/âûâîäà + + Ôóíêöèÿ îñòàíîâà ñèíõðîííûõ ïîòîêîâ ââîäà/âûâîäà äàííûõ. Ïîñëå âûïîëíåíèþ + ýòîé ôóíêöèè ìîäóëü çàâåðøàåò ãåíåðàöèþ îïîðíîé ÷àñòîòû ñèíõðîíèçàöèè (èëè + èñïîëüçîâàòü âíåøíþþ ÷àñòîòó ñèíõðîíèçàöèè) è îñòàíàâëèâàåò ñèíõðîííóþ + ïåðåäà÷ó äàííûõ. + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_StreamsStop(t_x502_hnd hnd); + + +/***************************************************************************//** + @brief Ïðîâåðêà, çàïóùåí ëè ñèíõðîííûé ââîä/âûâîä + + Ôóíêöèÿ ïðîâåðÿåò çàïóùåí ëè ñèíõðîííûé ââîä âûâîä ñ ïîìîùüþ X502_StreamsStart() + èëè êàêîé-ëèáî âíóòðåííåé ëîãèêîé â ïðîøèâêè BlackFin. + Åñëè ñáîð äàííûõ íå çàïóùåí, òî ôóíêöèÿ âîçâðàùàåò îøèáêó + #X502_ERR_STREAM_IS_NOT_RUNNING, åñëè çàïóùåí, òî íóëåâîé êîä îøèáêè + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_IsRunning(t_x502_hnd hnd); + + + +/***************************************************************************//** + @brief ×òåíèå äàííûõ ÀÖÏ è öèôðîâûõ âõîäîâ èç ìîäóëÿ + + Ôóíêöèÿ ñ÷èòûâàåò äàííûå îò ìîäóëÿ, êîòîðûå áûëè ïðèíÿòû â ïðîìåæóòî÷íûé + áóôåð â äðàéâåðå èëè áèáëèîòåêå. + Ôóíêöèÿ ïðèíèìàåò îòñ÷åòû â ñïåöèàëüíîì èíäåêñíîì ôîðìàòå, + â êîòîðîì ñîäåðæèòñÿ èíôîðìàöèÿ, ÷òî ýòî çà äàííûå (çíà÷åíèÿ öèôðîâûõ âõîäîâ + èëè îòñ÷åòû ÀÖÏ) è äîïîëíèòåëüíàÿ èíôîðìàöèÿ äëÿ ÀÖÏ (íîìåð êàíàëà, ðåæèì). + Äëÿ ðàçáîðà ïîëó÷åííûõ îòñ÷åòîâ èñïîëüçóåòñÿ ôóíêöèÿ X502_ProcessData(). + + Åñëè â áóôåðå ñåé÷àñ íàõîäèòñÿ ìåíüøå îòñ÷åòîâ, ÷åì áûëî çàïðîøåíî, + òî ôóíêöèÿ áóäåò îæèäàòü ïîêà ïðèäåò çàäàííîå êîëè÷åñòâî äàííûõ èëè + ïîêà íå èñòå÷åò óêàçàííûé òàéìàóò.  ïîñëåäíåì ñëó÷àå ôóíêöèÿ âîçâðàòèò + ñòîëüêî îòñ÷åòîâ, ñêîëüêî áûëî â áóôåðå ïðè èñòå÷åíèè òàéìàóòà. + + Êîëè÷åñòâî ãîòîâûõ äëÿ ÷òåíèÿ îòñ÷åòîâ â áóôåðå äðàéâåðà ìîæíî ïðè æåëàíèè + óçíàòü ñ ïîìîùüþ ôóíêöèè X502_GetRecvReadyCount(). + + Äî âûçîâîâ X502_Recv() ñèíõðîííûé ïîòîê ñáîðà äàííûõ äîëæåí áûòü óæå çàïóùåí + ñ ïîìîùüþ X502_StreamsStart(). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] buf Áóôåð, â êîòîðûå áóäóò ñîõðàíåíû îòñ÷åòû. + @param[in] size Êîëè÷åñòâî ñ÷èòûâàåìûõ îòñ÷åòîâ (32-áèòíûõ ñëîâ). + @param[in] tout Òàéìàóò íà ïðèåì äàííûõ â ìñ. + @return Åñëè < 0 - êîä îøèáêè. + Åñëè >= 0 - êîëè÷åñòâî ñ÷èòàííûõ ñëîâ. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_Recv(t_x502_hnd hnd, uint32_t* buf, uint32_t size, uint32_t tout); + + +/***************************************************************************//** + @brief Ïåðåäà÷à ïîòîêîâûõ äàííûõ ÖÀÏ è öèôðîâûõ âûõîäîâ â ìîäóëü + + Ôóíêöèÿ çàïèñûâàåò äàííûå íà ïåðåäà÷ó â ïðîìåæóòî÷íûé áóôåð, ïîñëå + ÷åãî ýòè äàííûå áóäóò ïåðåäàíû â ìîäóëü. + Äàííûå äîëæíû áûòü â ñïåöèàëüíîì ôîðìàòå, êîòîðûé îïðåäåëÿåò, ÷òî ýòî çà + äàííûå (öèôðîâûå âûõîäû, êàíàë ÖÀÏ1 èëè êàíàë ÖÀÏ2). Ïîäãîòîâèòü äàííûå + â íóæíîì ôîðìàòå ìîæíî ñ ïîìîùüþ X502_PrepareData(). + + Åñëè ïðîìåæóòî÷íûé áóôåð íà ïåðåäà÷ó çàïîëíåí, òî ôóíêöèÿ áóäåò æäàòü ïîêà + îí íå îñâîáîäèòñÿ èëè ïîêà íå èñòå÷åò óêàçàííûé òàéìàóò. + Êîëè÷åñòâî ñâîáîäíîãî ìåñòà â áóôåðå ìîæíî ïðè æåëàíèè óçíàòü ñ ïîìîùüþ + ôóíêöèè X502_GetSendReadyCount(). + + Âîçâðàùåíèå îçíà÷àåò, ÷òî äàííûå çàïèñàíû â ïðîìåæóòî÷íûé áóôåð, à íå + òî ÷òî îíè óæå äîøëè äî ìîäóëÿ è âûâåäåíû. + + Ïåðåä âûçîâîì ýòîé ôóíêöèè äîëæíà áûòü çàïóùåíà ïðåäçàãðóçêà äàííûõ íà + âûâîä ñ ïîìîùüþ X502_PreloadStart(). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] buf Áóôåð ñî ñëîâàìè, êîòîðûå íåîáõîäèìî ïåðåäàòü ìîäóëþ + @param[in] size Êîëè÷åñòâî ïåðåäàâàåìûõ îòñ÷åòîâ (32-áèòíûõ ñëîâ). + @param[in] tout Òàéìàóò íà ïåðåäà÷ó (â áóôåð äðàéâåðà) äàííûõ â ìñ. + @return Åñëè < 0 - êîä îøèáêè. + Åñëè >= 0 - êîëè÷åñòâî çàïèñàííûõ ñëîâ. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_Send(t_x502_hnd hnd, const uint32_t* buf, uint32_t size, uint32_t tout); + + + +/***************************************************************************//** + @brief Îáðàáîòêà ïðèíÿòûõ îòñ÷åòîâ ÀÖÏ îò ìîäóëÿ + + Ôóíêöèÿ âûïîëíÿåò îáðàáîòêó îòñ÷åòîâ ÀÖÏ, ïðî÷èòàííûõ ñ ïîìîùüþ X502_Recv(). + Ôóíêöèÿ ïðîâåðÿåò ñëóæåáíóþ èíôîðìàöèþ èç âõîäíîãî ìàññèâà è ïåðåâîäèò îòñ÷åòû + ÀÖÏ ëèáî â êîäû, ëèáî â âîëüòû (åñëè óêàçàí ôëàã #X502_PROC_FLAGS_VOLT). + + Ôóíêöèÿ èñïîëüçóåòñÿ, êîãäà íå çàïóùåí ñèíõðîííûé ââîä ñ öèôðîâûõ ëèíèé è + îòñ÷åòû ÀÖÏ ÿâëÿþòñÿ åäèíñòâåííûìè ïðèõîäÿùèìè îò ìîäóëÿ äàííûìè (åñëè + â ïðèíÿòîì ïîòîêå áóäóò äðóãèå äàííûå - òî îíè áóäóò îòáðîøåíû). + + Åñëè çàïóùåí ñèíõðîííûé ââîä ñ öèôðîâûõ ëèíèé, òî ñëåäóåò èñïîëüçîâàòü + X502_ProcessData(), êîòîðàÿ âûäåëÿåò äàííûå ñ öèôðîâûõ ëèíèé â îòäåëüíûé + ìàññèâ. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] src Âõîäíîé ìàññèâ îòñ÷åòîâ, ïðèíÿòûõ ñ ïîìîùüþ X502_Recv(). + @param[out] dest Ìàññèâ, â êîòîðûé áóäóò ñîõðàíåíû ïðåîáðàçîâàííûå äàííûå + îò ÀÖÏ. + @param[in,out] size Íà âõîäå - êîëè÷åñòâî ñëîâ â ìàññèâå src, íà âûõîäå - + êîëè÷åñòâî ñîõðàíåííûõ ïðåîáðàçîâàííûõ çíà÷åíèé â + ìàññèâå dest + @param[in] flags Íàáîð ôëàãîâ èç #t_x502_proc_flags + @return Êîä îøèáêè. + ****************************************************************************/ +X502_EXPORT(int32_t) X502_ProcessAdcData(t_x502_hnd hnd, const uint32_t* src, + double *dest, uint32_t *size, uint32_t flags); + +/***************************************************************************//** + @brief Îáðàáîòêà ïðèíÿòûõ îò ìîäóëÿ äàííûõ + + Ôóíêöèÿ âûïîëíÿåò îáðàáîòêó äàííûõ, ïðî÷èòàííûõ ñ ïîìîùüþ X502_Recv(). + Ôóíêöèÿ ïðîâåðÿåò ñëóæåáíóþ èíôîðìàöèþ èç âõîäíîãî ìàññèâà, ðàçáèâàåò äàííûå + íà äâà ìàññèâà - äàííûå îò ÀÖÏ, êîòîðûå ïåðåâîäÿòñÿ â òèï double, è äàííûå + îò ñèíõðîííîãî öèôðîâîãî ââîäà. + + Äàííûå îò ÀÖÏ òàê æå ìîãóò áûòü ïåðåâåäåíû â âîëüòû. Ïðè ýòîì äàííûå ÀÖÏ + ïðèõîäÿò îò ìîäóëÿ óæå îòêàëèáðîâàííûìè ñ ïîìîùüþ êàëèáðîâî÷íûõ êîýôôèöèåíòîâ, + òàê êàê êàëèáðîâêà âûïîëíÿåòñÿ àïïàðàòíî. Åñëè äàííûå ÀÖÏ íå ïåðåâîäÿòñÿ + â Âîëüòû è ïðè ýòîì íå áûëè èçìåíåíû çàâîäñêèå êàëèáðîâî÷íûå êîýôôèöèåíòû, + òî âîçâðàùåííîå çíà÷åíèå ðàâíîå #X502_ADC_SCALE_CODE_MAX ñîîòâåòñòâóåò + íàïðÿæåíèþ ðàâíîìó ìàêñèìàëüíîìó äëÿ èñïîëüçóåìîãî äèàïàçîíà. + + Êðîìå òîãî, ôóíêöèÿ ðàçáèðàåò ñîîáùåíèÿ, ïåðåäàâàåìûå â ïîòîêå äàííûõ + (íàïðèìåð, ñîîáùåíèå î ïåðåïîëíåíèè áóôåðà). + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] src Âõîäíîé ìàññèâ îòñ÷åòîâ, ïðèíÿòûé ñ ïîìîùüþ X502_Recv(). + @param[in] size Êîëè÷åñòâî îòñ÷åòîâ (32-áèòíûõ ñëîâ) â ìàññèâå src. + @param[in] flags Íàáîð ôëàãîâ èç #t_x502_proc_flags, óïðàâëÿþùèõ + ïîâåäåíèåì ôóíêöèè. Ìîæåò áûòü óêàçàíî íåñêîëüêî ôëàãîâ + ÷åðåç ëîãè÷åñêîå "ÈËÈ". + @param[out] adc_data Ìàññèâ, â êîòîðûé áóäóò ñîõðàíåíû äàííûå îò ÀÖÏ, + ïðåîáðàçîâàííûå â ñîîòâåòñòâèè ñ óêàçàííûìè ôëàãàìè. + Ìîæåò áûòü NULL, åñëè íå íóæíî ñîõðàíÿòü äàííûå îò ÀÖÏ + (òîãäà adc_data_size äîëæåí áûòü òîæå NULL, èëè â + ïåðåìåííîé ïåðåäàí ðàçìåð 0). + @param[in,out] adc_data_size Íà âõîäå â äàííîì ïàðàìåòðå ïåðåäàåòñÿ ðåçìåð + áóôåðà adc_data. Åñëè äàííûõ îò ÀÖÏ âî âõîäíîì ìàññèâå + áóäåò áîëüøå adc_data_size, òî â adc_data áóäåò + ñîõðàíåíî òîëüêî ïåðâûå adc_data_size îòñ÷åòîâ. + Íà âûõîäå ïðè óñïåøíîì çàâåðøåíèè ôóíêöèè â äàííóþ + ïåðåìåííóþ çàïèñûâàåòñÿ êîëè÷åñòâî ñîõðàííûõ îòñ÷åòîâ + ÀÖÏ. + Óêàçàòåëü ìîæåò áûòü ðàâåí NULL, åñëè adc_data = NULL + @param[out] din_data Ìàññèâ, â êîòîðûé áóäóò ñîõðàíåíû îò÷åòû ñ ñèíõðîííîãî + öèôðîâîãî ââîäà. Êàæäîå ñëîâî ñîîòâåòñòâóþò ñîñòîÿíèþ + âñåõ öèôðîâûõ âõîäîâ â ôîðìàòå, îïèñàííîì â ôóíêöèè + X502_AsyncInDig(). + @param[in,out] din_data_size Àíàëîãè÷íî ïàðàìåòðó adc_data_size â ýòîì + ïàðàìåòðå ïåðåäàåòñÿ ðàçìåð áóôåðà din_data â îòñ÷åòàõ, + à íà âûõîäå ñîõðàíÿåòñÿ êîëè÷åñòâî ðåàëüíî ñîõðàíåííûõ + îòñ÷åòîâ öèôðîâîãî ââîäà. Ìîæåò áûòü NULL, åñëè + din_data = NULL. + @return Êîä îøèáêè. + ****************************************************************************/ +X502_EXPORT(int32_t) X502_ProcessData(t_x502_hnd hnd, const uint32_t* src, uint32_t size, + uint32_t flags, double *adc_data, uint32_t *adc_data_size, + uint32_t *din_data, uint32_t *din_data_size); + +/***************************************************************************//** + @brief Îáðàáîòêà ïðèíÿòûõ îò ìîäóëÿ äàííûõ ñ ïîëüçîâàòåëüñêèìè äàííûìè + + Ôóíêöèÿ àíàëîãè÷íà X502_ProcessData(), íî ïîçâîëÿåò òàêæå âûäåëèòü + ïîëüçîâàòåëüñêèå äàííûå èç ïîòîêà. Ïîëüçîâàòåëüñêèìè äàííûìè ñ÷èòàþòñÿ âñå + îòñ÷åòû, êîòîðûå íå ÿâëÿþòñÿ äàííûìè ÀÖÏ, äàííûìè öèôðîâîãî ââîäà + èëè ñîîáùåíèÿìè. + Ïîëüçîâàòåëüñêèå äàííûå ñêëàäûâàþòñÿ áåç èçìåíåíèé â ìàññèâ usr_data + (åñëè îí íå ðàâåí íóëþ). + Äàííàÿ ôóíêöèÿ ïðåäíàçíà÷åíà â ïåðâóþ î÷åðåäü äëÿ ïðîãðàììèñòîâ, êîòîðûå + áóäóò èñïîëüçîâàòü ìîäèôèöèðîâàííóþ ïðîøèâêó ñèãíàëüíîãî ïðîöåññîðà BlackFin. + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] src Âõîäíîé ìàññèâ îòñ÷åòîâ, ïðèíÿòûé ñ ïîìîùüþ + X502_Recv(). + @param[in] size Êîëè÷åñòâî îòñ÷åòîâ (32-áèòíûõ ñëîâ) â ìàññèâå src. + @param[in] flags Íàáîð ôëàãîâ èç #t_x502_proc_flags. + @param[out] adc_data Ìàññèâ, â êîòîðûé áóäóò ñîõðàíåíû äàííûå îò ÀÖÏ + (ñì. X502_ProcessData()). + @param[in,out] adc_data_size ñì. X502_ProcessData() + @param[out] din_data Ìàññèâ, â êîòîðûé áóäóò ñîõðàíåíû îò÷åòû ñ + ñèíõðîííîãî öèôðîâîãî ââîäà. Ñì. X502_ProcessData(). + @param[in,out] din_data_size ñì. X502_ProcessData(). + @param[out] usr_data Ìàññèâ, â êîòîðûé áóäóò ñîõðàíåíû ïîëüçîâàòåëüñêèå + äàííûå áåç èçìåíåíèÿ èõ ôîðìàòà. + @param[in,out] usr_data_size  ýòîì ïàðàìåòðå ïåðåäàåòñÿ ðàçìåð áóôåðà usr_data + à íà âûõîäå ñîõðàíÿåòñÿ êîëè÷åñòâî ðåàëüíî ñîõðàíåííûõ + îòñ÷åòîâ ïîëüçîâàòåëüñêèõ äàííûõ. + Ìîæåò áûòü NULL òîëüêî åñëè usr_data = NULL. + @return Êîä îøèáêè. + ****************************************************************************/ +X502_EXPORT(int32_t) X502_ProcessDataWithUserExt(t_x502_hnd hnd, const uint32_t* src, uint32_t size, + uint32_t flags, double *adc_data, + uint32_t *adc_data_size, uint32_t *din_data, + uint32_t *din_data_size, + uint32_t *usr_data, uint32_t *usr_data_size); + + + +/***************************************************************************//** + @brief Ïîäãîòîâêà äàííûõ äëÿ âûâîäà â ìîäóëü + + Ôóíêöèÿ ïðèíèìàåò äàííûå èç òðåõ ìàññèâîâ - äàííûå íà öèôðîâûå âûõîäû, + îòñ÷åòû ïåðâîãî è âòîðîãî êàíàëà ÖÀÏ.  êà÷åñòâå ìàññèâà ìîæåò áûòü ïåðåäàí + íóëåâîé óêàçàòåëü, åñëè äàííûå èç ýòîãî èñòî÷íèêà íå òðåáóþòñÿ. + Âñå èñïîëüçóåìûå ìàññèâû äîëæíû áûòü îäèíàêîâîãî ðàçìåðà è ôóíêöèÿ èõ + ðàâíîìåðíî ïåðåìåøèâàåò â îáùèé ïîòîê, ïðåîáðàçóÿ â íóæíûé äëÿ ìîäóëÿ ôîðìàò. + + Âûõîäíîé ìàññèâ äîëæåí áóäåò ñîäåðæàòü n*size îòñ÷åòîâ, ãäå n - êîëè÷åñòâî + èñïîëüçóåìûõ âõîäíûõ ìàññèâîâ (îò 1 äî 3). + + Çíà÷åíèÿ öèôðîâûõ âûõîäîâ ïðåäñòàâëÿþò ñîáîé 32-áèòíûå ñëîâà, ìëàäøèå 16-áèò + êîòîðûõ îïðåäåëÿþò çíà÷åíèÿ âûâîäîâ, à ñòàðøèå - ôëàãè èç + #t_x502_digout_word_flags, êîòîðûå ìîãóò èñïîëüçîâàòüñÿ â ÷àñòíîñòè äëÿ + ïåðåâîäà îäíîé (èëè îáåèõ) èç ïîëîâèí âûâîäîâ â òðåòüå ñîñòîÿíèå. + +  êà÷åñòâå çíà÷åíèé ÖÀÏ ìîãóò èñïîëüçîâàòüñÿ êàê êîäû, òàê è Âîëüòû, + â çàâèñèìîñòè îò ïåðåäàííûõ ôëàãîâ, è ê âûâîäèìûì çíà÷åíèÿì ìîãóò áûòü + ïðèìåíåíû êàëèáðîâî÷íûå êîýôôèöèåíòû. + Åñëè èñïîëüçóþòñÿ êîäû ÖÀÏ ñ âêëþ÷åííîé êàëèáðîâêîé, òî êîä + #X502_DAC_SCALE_CODE_MAX îïðåäåëÿåò êîä, ñîîòâåòñòâóþùèé +5V. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] dac1 Âõîäíîé ìàññèâ îòñ÷åòîâ ïåðâîãî êàíàëà ÖÀÏ èëè + NULL, åñëè íå èñïîëüçóåòñÿ. + @param[in] dac2 Âõîäíîé ìàññèâ îòñ÷åòîâ âòîðîãî êàíàëà ÖÀÏ èëè + NULL, åñëè íå èñïîëüçóåòñÿ. + @param[in] digout Âõîäíîé ìàññèâ ñî çíà÷åíèÿìè öèôðîâûõ âûâîäîâ + èëè NULL, åñëè íå èñïîëüçóåòñÿ. + @param[in] size Ðàçìåð êàæäîãî èç èñïîëüçóåìûõ âõîäíûõ ìàññèâîâ. + @param[in] flags Ôëàãè, óïðàâëÿþùèå ðàáîòîé ôóíêöèè, èç + #t_x502_dacout_flags. + @param[out] out_buf Âûõîäíîé ìàññèâ, â êîòîðûé áóäóò ñîõðàíåíû + ñôîðìèðîâàííûå îòñ÷åòû. Äîëæåí áûòü ðàçìåðà + n*size (n - êîëè÷åñòâî èñïîëüçóåìûõ âõîäíûõ + ìàññèâîâ) + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_PrepareData(t_x502_hnd hnd, const double* dac1, const double* dac2, + const uint32_t* digout, uint32_t size, int32_t flags, + uint32_t* out_buf); + +/***************************************************************************//** + @brief Ïîäãîòîâêà äàííûõ äëÿ ïåðåäà÷è â ìîäóëü + + Ôóíêöèÿ ïðèíèìàåò îòñ÷åòû ÖÀÏ è öèôðîâûõ ëèíèé è íà îñíîâå ýòèõ äàííûõ + ôîðìèðóåò ñëîâà ñïåöèàëüíîãî ôîðìàòà äëÿ äàëüíåéøåé ïåðåäà÷è â ìîäóëü ñ + ïîìîùüþ X502_Send(). + + Äàííûå îò ÖÀÏ ïðèíèìàþòñÿ â ñëåäóþùåì ïîðÿäêå: 1-ûé îòñ÷åò ïåðâîãî ðàçðåøåííîãî + êàíàëà, çàòåì 1-ûé îòñ÷åò âòîðîãî òàêîãî êàíàëà + è ò.ä., çàòåì ïî âòîðîìó îòñ÷åòó êàæäîãî êàíàëà è ò.ä. +  êà÷åñòâå çíà÷åíèé ÖÀÏ ìîãóò èñïîëüçîâàòüñÿ êàê êîäû, òàê è Âîëüòû, + â çàâèñèìîñòè îò ïåðåäàííûõ ôëàãîâ, è ê âûâîäèìûì çíà÷åíèÿì ìîãóò áûòü + ïðèìåíåíû êàëèáðîâî÷íûå êîýôôèöèåíòû. + + Çíà÷åíèÿ öèôðîâûõ âûõîäîâ ïðåäñòàâëÿþò ñîáîé 32-áèòíûå ñëîâà, ìëàäøèå 16-áèò + êîòîðûõ îïðåäåëÿþò çíà÷åíèÿ âûâîäîâ, à ñòàðøèå - ôëàãè èç + #t_x502_digout_word_flags, êîòîðûå ìîãóò èñïîëüçîâàòüñÿ â ÷àñòíîñòè äëÿ + ïåðåâîäà îäíîé (èëè îáåèõ) èç ïîëîâèí âûâîäîâ â òðåòüå ñîñòîÿíèå. + + Ôîðìàò âûõîäíûõ ñëîâ è êîëè÷åñòâî ñëîâ íà îòñ÷åò îïðåäåëÿåòñÿ çàäàííûì + [ôîðìàòîì ïåðåäàâàåìûõ äàííûõ](@ref TLTR35_CONFIG::OutDataFmt). + Ôóíêöèÿ çàâåðøàåò ðàáîòó êàê òîëüêî çàêîí÷èòñÿ ìåñòî â âûõîäíîì áóôåðå. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] dac_data Ìàññèâ îòñ÷åòîâ ÖÀÏ. Ìîæåò áûòü ïåðåäàí íóëåâîé óêàçàòåëü, + åñëè èñïîëüçóþòñÿ òîëüêî âûâîä íà öèôðîâûå ëèíèè. + @param[in,out] dac_size Íà âõîäå çàäàåò êîëè÷åñòâî îòñ÷åòîâ â ìàññèâå dac_data, + íà âûõîäå âîçâðàùàåò, ñêîëüêî îòñ÷åòîâ áûëî ðåàëüíî + çàïèñàíî â âûõîäíîé ìàññèâ. + @param[in] dout_data Ìàññèâ çíà÷åíèé äëÿ âûâîäà íà öèôðîâûå ëèíèè. + Ìîæåò áûòü ïåðåäàí íóëåâîé óêàçàòåëü, + åñëè èñïîëüçóþòñÿ òîëüêî äàííûå ÖÀÏ. + @param[in,out] dout_size Íà âõîäå çàäàåò êîëè÷åñòâî çíà÷åíèé â ìàññèâå dout_data, + íà âûõîäå âîçâðàùàåò, ñêîëüêî çíà÷åíèé áûëî ðåàëüíî + çàïèñàíî â âûõîäíîé ìàññèâ. + @param[in] flags Ôëàãè, óïðàâëÿþùèå ðàáîòîé ôóíêöèè, èç + #t_x502_dacout_flags. + @param[out] result Ðåçóëüòèðóþùèé ìàññèâ â ñïåöèàëüíîì ôîðìàòå äëÿ + ïåðåäà÷è â ìîäóëü. + @param[in,out] snd_size Íà âõîäå çàäàåò ðàçìåð ìàññèâà result, íà âûõîäå + âîçâðàùàåò, ñêîëüêî âñåãî 32-áèòíûõ ñëîâ áûëî çàïèñàíî + â ìàññèâ result â õîäå âûïîëíåíèÿ ôóíêöèè. + @param[in] ch_enabled Áèòîâàÿ ìàñêà îïðåäåëÿþùàÿ ðàçðåøåííûå êàíàëû, + 1é áèò - 1é êàíàë, 2é áèò - 2é êàíàë, è ò.ä. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_PrepareData2(t_x502_hnd hnd, const double *dac_data, + uint32_t *dac_size, const uint32_t *dout_data, + uint32_t *dout_size, uint32_t flags, + uint32_t *result, uint32_t *snd_size, uint32_t ch_enabled); + +/***************************************************************************//** + @brief Ïîäãîòîâêà îäíîãî ñëîâà äëÿ âûâîäà â ÖÀÏ + + Ôóíêöèÿ ïðèíèìàåò îäèí îòñ÷åò äëÿ îäíîãî êàíàëà ÖÀÏ. +  êà÷åñòâå çíà÷åíèÿ îòñ÷åòà ÖÀÏ ìîãóò èñïîëüçîâàòüñÿ êàê êîäû, òàê è Âîëüòû, + â çàâèñèìîñòè îò ïåðåäàííûõ ôëàãîâ, è ê âûâîäèìûì çíà÷åíèÿì ìîãóò áûòü + ïðèìåíåíû êàëèáðîâî÷íûå êîýôôèöèåíòû. + Åñëè èñïîëüçóþòñÿ êîäû ÖÀÏ ñ âêëþ÷åííîé êàëèáðîâêîé, òî êîä + #X502_DAC_SCALE_CODE_MAX îïðåäåëÿåò êîä, ñîîòâåòñòâóþùèé +5V. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] range Äèàïàçîí èëè âûõîäû îäíîãî êàíàëà (äëÿ LTA37 1:1 èëè 1:10) (èç #t_x502devs_dac_range) + @param[in] dac_ch Íîìåð êàíàëà ÖÀÏ. + @param[in] dac_val Îòñ÷åò êàíàëà dac_ch ÖÀÏ. + @param[in] flags Ôëàãè, óïðàâëÿþùèå ðàáîòîé ôóíêöèè, èç + #t_x502_dacout_flags. + @param[out] out_buf Óêàçàòåëü íà ðåçóëüòèðóþùåå ñëîâî äàííûõ äëÿ êàíàëà dac_ch. + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_PrepareDacWrd(t_x502_hnd hnd, uint32_t dac_ch, double dac_val, + int32_t flags, + uint32_t* out_wrd); + +/***************************************************************************//** + @brief Ïîëó÷èòü êîëè÷åñòâî îòñ÷åòîâ â áóôåðå ïîòîêà íà ââîä + + Ôóíêöèÿ âîçâðàùàåò êîëè÷åñòâî îòñ÷åòîâ, êîòîðûå áûëè ïðèíÿòû èç ìîäóëÿ + âî âíóòðåííèé áóôåð è ãîòîâû äëÿ ñ÷èòûâàíèÿ ñ ïîìîùüþ X502_Recv(). + Òî åñòü åñëè â X502_Recv() ïåðåäàòü çíà÷åíèå, êîòîðîå âåðíóëà äàííàÿ ôóíêöèÿ, + òî X502_Recv() âåðíåò ýòî êîëè÷åñòâî äàííûõ áåç îæèäàíèÿ (òàê êàê îíè óæå + â áóôåðå). + Ïðè ðàáîòå ïî Ethernet äàííàÿ ôóíêöèÿ âîçâðàùàåò êîððåêòíîå çíà÷åíèå òîëüêî + äëÿ ÎÑ Windows. + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] rdy_cnt Êîëè÷åñòâî ãîòîâûõ ê ïðèåìó îòñ÷åòîâ. + @return Êîä îøèáêè. + ***************************************************************************/ +X502_EXPORT(int32_t) X502_GetRecvReadyCount(t_x502_hnd hnd, uint32_t *rdy_cnt); + + +/***************************************************************************//** + @brief Ïîëó÷èòü ðàçìåð ñâîáîäíîãî ìåñòà â áóôåðå ïîòîêà íà âûâîä + + Ôóíêöèÿ âîçâðàùàåò êîëè÷åñòâî îòñ÷åòîâ, ñîîòâåòñòâóþùåå ñâîáîäíîìó ìåñòó + â áóôåðå íà ïåðåäà÷ó â ìîäóëü. + Ýòî êîëè÷åñòâî îòñ÷åòîâ ãàðàíòèðîâàííî ìîæåò áûòü ïåðåäàíî ñ ïîìîùüþ + X502_Send() áåç îæèäàíèÿ. + Äàííàÿ ôóíêöèÿ íå ðåàëèçîâàíà ïðè ðàáîòå ïî èíòåðôåéñó Ethernet (TCP). + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] rdy_cnt Êîëè÷åñòâî ñëîâ, êîòîðîìó ñîîòâåòñòâóåò + ñâîáîäíîå ìåñòî â áóôåðå íà ïåðåäà÷ó. + @return Êîä îøèáêè. + ***************************************************************************/ +X502_EXPORT(int32_t) X502_GetSendReadyCount(t_x502_hnd hnd, uint32_t *rdy_cnt); + +/***************************************************************************//** + @brief Ïîëó÷èòü íîìåð ñëåäóþùåãî îæèäàåìîãî ëîãè÷åñêîãî êàíàëà ÀÖÏ äëÿ + îáðàáîòêè + + Ôóíêöèÿ âîçâðàùàåò íîìåð ëîãè÷åñêîãî êàíàëà ÀÖÏ, êîòîðûé äîëæåí áûòü + îáðàáîòàí ïåðâûì ïðè ñëåäóþùåì âûçîâå X502_ProcessData()/ + X502_ProcessAdcData() â ñëó÷àå, åñëè ïîòîê äàííûõ íåïðåðûâåí. + + Ïî ñóòè, ýòî íîìåð ëîãè÷åñêîãî êàíàëà, ñëåäóþùèé çà ëîãè÷åñêèì êàíàëîì + ïîñëåäíåãî îáðàáîòàííîãî äî ýòîãî îòñ÷åòà ÀÖÏ. + Ìîæåò áûòü èñïîëüçîâàíî ïðè îáðàáîòêå áëîêîâ äàííûõ íå êðàòíûõ öåëîìó + êîëè÷åñòâó êàäðîâ. + Åñëè ïåðåä X502_ProcessData() âûçûâàòü äàííóþ ôóíêöèþ, òî îíà âåðíåò íîìåð + ëîãè÷åñêîãî êàíàëà, ñîîòâåòñòâóþùèé ïåðâîìó îòñ÷åòó ÀÖÏ, + îáðàáîòàííîìó ïîñëåäóþùèì âûçîâîì X502_ProcessData(). + + Íàïðèìåð, åñëè óñòàíîâëåíî 7 ëîãè÷åñêèõ êàíàëîâ, à â X502_ProcessData() + ïåðåäàíî äëÿ îáðàáîòêè êðàòíîå 7 êîëè÷åñòâî îòñ÷åòîâ, òî ïîñëåäóþùèé âûçîâ + X502_GetNextExpectedLchNum() âåðíåò íîìåð êàíàëà ðàâíûé 0 (òàê êàê îáðàáîòàíî + öåëîå ÷èñëî êàäðîâ è îæèäàåòñÿ ñíîâà íà÷àëî êàäðà). + Åñëè â X502_ProcessData() ïåðåäàí ìàññèâ ñ 7*n + 5 îòñ÷åòàìè ÀÖÏ, òî ñëåäóþùèì + îæèäàåìûì êàíàëîì áóäåò ëîãè÷åñêèé êàíàë ñ íîìåðîì 5 (îáðàáîòàíû êàíàëû + 0,1,2,3,4 èç íåïîëíîãî êàäðà). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] lch Íîìåð ëîãè÷åñêîãî êàíàëà (íà÷èíàÿ ñ íóëÿ). + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_GetNextExpectedLchNum(t_x502_hnd hnd, uint32_t *lch); + + + +/***************************************************************************//** + @brief Íà÷àëî ïîäãîòîâêè âûâîäà ñèíõðîííûõ äàííûõ + + Ôóíêöèÿ äîëæíà âûçûâàòüñÿ ïåðåä íà÷àëîì ïðåäçàãðóçêè ïîòîêîâûõ ñèíõðîííûõ + äàííûõ íà âûâîä. Äëÿ íà÷àëà âûäà÷è ñèíõðîííûõ äàííûõ îäíîâðåìåííî ñ íà÷àëîì + ñèíõðîííîãî ââîäà, ê ìîìåíòó íà÷àëà ñáîðà ÷àñòü äàííûõ äîëæíà áûòü óæå + çàãðóæåíà â ìîäóëü äî âûçîâà X502_StreamsStart(). + + Äàííàÿ ôóíêöèÿ èíèöèàëèçèðóåò êàíàë îáìåíà íà ïåðåäà÷ó äàííûõ íà âûâîä. + Ïîñëå âûçîâà ýòîé ôóíêöèè ìîæíî çàãðóçèòü ÷àñòü äàííûõ íà âûâîä + ñ ïîìîùüþ X502_Send(). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @return Êîä îøèáêè. + ***************************************************************************/ +X502_EXPORT(int32_t) X502_PreloadStart(t_x502_hnd hnd); + + + +/***************************************************************************//** + @brief Íà÷àëî çàãðóçêè öèêëè÷åñêîãî ñèãíàëà íà âûâîä + + Ïî âûçîâó ýòîé ôóíêöèè â äðàéâåðå (äëÿ L502) èëè â ïàìÿòè êîíòðîëëåðà ìîäóëÿ + (äëÿ E502) âûäåëÿåòñÿ ìåñòî ïîä öèêëè÷åñêèé áóôåð íà âûâîä. + Äîëæíà âûçûâàòüñÿ ïåðåä çàãðóçêîé öèêëè÷åñêèõ äàííûõ ñ ïîìîùüþ + X502_Send(). + + Äëÿ óñïåøíîãî âûïîëíåíèÿ äîëæåí áûòü ñâîáîäíûé áóôåð (èñïîëüçóåòñÿ + äâîéíàÿ áóôåðèçàöèÿ) - ò.å. ôóíêöèÿ íå ìîæåò áûòü âûçâàíà ñðàçó ïîñëå ïðåäûäóùåãî + X502_OutCycleSetup(). Êðîìå òîãî íå äîëæåí áûë áûòü èñïîëüçîâàí ïîòîêîâûé + âûâîä. + + Äëÿ L-502 ðàçìåð ìàêñèìàëüíûé ðàçìåð áóôåðà îïðåäåëÿåòñÿ òîëüêî ðàçìåðîì, + êîòîðûé ïîçâîëÿåò âûäåëèòü ñèñòåìà íà óðîâíå äðàéâåðà. + Äëÿ E-502 ðàçìåð îãðàíè÷åí ïàìÿòüþ âñòðîåííîãî êîíòðîëëåðà. + Ïîäðîáíåå ñìîòðè â îïèñàíèè ðàçëè÷èé E-502 è L-502. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] size Êîëè÷åñòâî îòñ÷åòîâ â âûâîäèìîì öèêëè÷åñêîì ñèãíàëå + ñóììàðíî äëÿ âñåõ èñïîëüçóåìûõ êàíàëîâ âûâîäà. + @return Êîä îøèáêè. + ***************************************************************************/ +X502_EXPORT(int32_t) X502_OutCycleLoadStart(t_x502_hnd hnd, uint32_t size); + +/***************************************************************************//** + @brief Óñòàíîâêà ðàíåå çàãðóæåííîãî öèêëè÷åñêîãî ñèãíàëà íà âûâîä + + Ïî âûçîâó ýòîé ôóíêöèè ðàíåå çàãðóæåííûé öèêëè÷åñêèé áóôåð ñòàíîâèòñÿ àêòèâíûì. + Åñëè ñèíõðîííûé ââîä-âûâîä çàïóùåí (÷åðåç X502_StreamsStart()), òî ïî ýòîé + ôóíêöèè ñèãíàë áóäåò âûäàâàòüñÿ íà âûõîä, èíà÷å âûäà÷à íà÷íåòñÿ ïðè çàïóñêå + ñèíõðîííîãî ââîäà-âûâîäà. + + Åñëè äî ýòîãî óæå âûâîäèëñÿ öèêëè÷åñêèé ñèãíàë, òî ñìåíà íà íîâûé ïðîèçîéäåò + â êîíöå öèêëà ïðåäûäóùåãî ñèãíàëà, åñëè íå óêàçàí ôëàã + #X502_OUT_CYCLE_FLAGS_FORCE. + + Åñëè íå óêàçàí ôëàã #X502_OUT_CYCLE_FLAGS_WAIT_DONE, òî ôóíêöèÿ òîëüêî äàåò + êîìàíäó íà óñòàíîâêó ñèãíàëà, íå îæèäàÿ íåïîñðåäñòâåííîé ñìåíû ñèãíàëà èëè + çàãðóçêè ñèãíàëà.  ÷àñòíîñòè äëÿ îäíîâðåìåííîãî çàïóñêà ñèíõðîííîãî ââîäà + è âûâîäà íåîáõîäèìî ñäåëàòü çàãðóçêó ïåðâîãî öèêëè÷åñêîãî ñèãíàëà ñ äàííûì + ôëàãîì, ÷òîáû ãàðàíòèðîâàòü, ÷òî ñèãíàë ïîëíîñòüþ çàãðóæåí ê ìîìåíòó çàïóñêà + ñèíõðîííîãî ââîäà-âûâîäà ÷åðåç X502_StreamsStart(). + + Äàííàÿ ôóíêöèÿ äîëæíà áûòü âûçâàíà òîëüêî ïîñëå âûçîâà X502_OutCycleLoadStart() + è çàãðóçêè óêàçàííîãî â íåé êîëè÷åñòâà îòñ÷åòîâ â áóôåð! + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] flags Ôëàãè èç #t_x502_out_cycle_flags. + @return Êîä îøèáêè. + ***************************************************************************/ +X502_EXPORT(int32_t) X502_OutCycleSetup(t_x502_hnd hnd, uint32_t flags); + + +/***************************************************************************//** + @brief Îæèäàíèå óñòàíîâêè ðàíåå çàãðóæåííîãî öèêëè÷åñêîãî ñèãíàëà íà âûâîä + + Ðåàëèçóåò òîò æå ôóíêöèîíàë ÷òî è X502_OutCycleSetup ñ ôëàãîì #X502_OUT_CYCLE_FLAGS_WAIT_DONE + Äîáàâëåí òàéìàóò îæèäàíèÿ óñòàíîâêè öèêëè÷åñêîãî ñèãíàëà. + + Äàííàÿ ôóíêöèÿ äîëæíà áûòü âûçâàíà òîëüêî ïîñëå âûçîâà X502_OutCycleLoadStart() è X502_OutCycleSetup() + è çàãðóçêè óêàçàííîãî â íåé êîëè÷åñòâà îòñ÷åòîâ â áóôåð! + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] flags Ôëàãè èç #t_x502_out_cycle_flags. + @param[in] tout Òàéìàóò â òå÷åíèè êîòîðîãî áóäåò îæèäàòüñÿ, êîãäà + ïðîöåññîð çàâåðøèò âûïîëíåíèå êîìàíäû. Ôóíêöèÿ + âîçâðàòèò óïðàâëåíèå ëèáî ïî çàâåðøåíèþ êîìàíäû, + ëèáî ïî òàéìàóòó. + @return Êîä îøèáêè. + ***************************************************************************/ +X502_EXPORT(int32_t) X502_OutCycleSetupWaitDone(t_x502_hnd hnd, uint32_t tout); + +/***************************************************************************//** + @brief Îñòàíîâ âûâîäà öèêëè÷åñêîãî ñèãíàëà + + Ïî âûçîâó ýòîé ôóíêöèè ïðåêðàùàåòñÿ âûäà÷à ðàíåå óñòàíîâëåííîãî öèêëè÷åñêîãî + ñèãíàëà ñ ïîìîùüþ X502_OutCycleSetup(). Îñòàíîâêà îñóùåñòâëÿåòñÿ + ïîñëå âûäà÷è ïîñëåäíåãî îòñ÷åòà â ïåðèîäå, ÷òî ïîçâîëÿåò çíàòü êàêèå çíà÷åíèÿ + îñòàíóòñÿ íà âûõîäàõ. + + Ïðè âûçîâå æå X502_StreamsStop() (èëè ïðè çàïðåùåíèè âñåõ ïîòîêîâ + íà âûâîä ÷åðåç X502_StreamsDisable()) îñòàíîâ âñåõ ïîòîêîâ ïðîèñõîäèò ñðàçó + è òî÷íàÿ òî÷êà îñòàíîâà íåèçâåñòíà. + + Ïðè ýòîì íåîáõîäèìî ó÷èòûâàòü, ÷òî ñàìà ôóíêöèÿ ïî óìîë÷àíèþ òîëüêî äåëàåò çàïðîñ + íà îñòàíîâ, à ðåàëüíî îñòàíîâ ïðîèçîéäåò ïîçæå. Åñëè âûçâàòü X502_StreamsStop() + äî çàâåðøåíèÿ îñòàíîâà, òî ïîñëåäíèé îòñ÷åò áóäåò íåèçâåñòåí, ò.å. íåîáõîäèìî + äîæäàòüñÿ çàâåðøåíèÿ îñòàíîâà, äëÿ ÷åãî ìîæåò íàïðèìåð áûòü èñïîëüçîâàí + ôëàã #X502_OUT_CYCLE_FLAGS_WAIT_DONE. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] flags Ôëàãè èç #t_x502_out_cycle_flags. + @return Êîä îøèáêè. + ***************************************************************************/ +X502_EXPORT(int32_t) X502_OutCycleStop(t_x502_hnd hnd, uint32_t flags); + + +/***************************************************************************//** + @brief Ïðîâåðêà, çàâåðøåíà ëè óñòàíîâêà èëè îñòàíîâ öèêëè÷åñêîãî ñèãíàëà + + Ôóíêöèÿ ïðîâåðÿåò, çàâåðøåíà ëè óñòàíîâêà öèêëè÷åñêîãî ñèãíàëà ïîñëå âûçîâà + X502_OutCycleSetup() èëè çàâåðøåíà ëè îñòàíîâêà ãåíåðàöèè öèêëè÷åñêîãî + ñèãíàëà ïîñëå âûçîâà X502_OutCycleStop(). Ïî ñâîåìó íàçíà÷åíèþ àíàëîãè÷íà ôëàãó + #X502_OUT_CYCLE_FLAGS_WAIT_DONE â îïèñàííûõ âûøå ôóíêöèÿõ, íî ïîçâîëÿåò + âûïîëíèòü îæèäàíèå âðó÷íóþ (ñ ïðîâåðêîé äðóãèõ óñëîâèé). + + Ôóíêöèÿ äîñòóïíà â áèáëèîòåêè, íà÷èíàÿ ñ âåðñèè 1.1.2, ïðè ýòîì äëÿ ðàáîòû + ôóíêöèè íåîáõîäèìà âåðñèÿ ïðîøèâêè ARM íå íèæå 1.0.2 äëÿ ìîäóëÿ E-502 èëè + âåðñèÿ äðàéâåðà íå íèæå 1.0.9 äëÿ L-502.  îòëè÷èå îò ôëàãà, åñëè äàííûå óñëîâèÿ + íå âûïîëíÿþòñÿ, òî ôóíêöèÿ ÿâíî âåðíåò îøèáêó #X502_ERR_NOT_SUP_BY_FIRMWARE + èëè #X502_ERR_NOT_SUP_BY_DRIVER. + + Îæèäàíèå çàâåðøåíèÿ ìîæåò áûòü íåîáõîäèìî ïðè âûçîâå X502_OutCycleSetup() + ïðè çàãðóçêå ïåðâîãî ñèãíàëà äî âûçîâà X502_StreamsStart(), åñëè òðåáóåòñÿ, + ÷òîáû âûäà÷à ïåðâûõ îòñ÷åòîâ íà ÖÀÏ ñîâïàëà ïî âðåìåíè ñ ìîìåíòîì çàïóñêà ââîäà, + ò.ê. èíà÷å çàãðóçêà ñèãíàëà â ìîäóëü ìîæåò íå çàâåðøèòüñÿ ê ìîìåíòó çàïóñêà + è âûäà÷à ñèãíàëà íà÷íåòñÿ ñ çàäåðæêîé (àêòóàëüíî äëÿ E502). + + Ïðè ïîñëåäóþùèõ âûçîâàõ X502_OutCycleSetup() äëÿ ñìåíû óæå óñòàíîâëåííîãî + ñèãíàëà óñòàíîâêà ñ÷èòàåòñÿ çàâåðøåííîé ïîñëå çàâåðøåíèÿ çàãðóçêè ñèãíàëà è + íåïîñðåäñòâåííîé ñìåíû âûäàâàåìîãî ñèãíàëà. Ýòó ïðîâåðêó ìîæíî èñïîëüçîâàòü, + ÷òîáû ÿâíî óçíàòü, ÷òî ñìåíà ñèãíàëà çàâåðøèëàñü è ìîæíî óæå çàãðóæàòü + ñëåäóþùèé öèêëè÷åñêèé ñèãíàë. + + Ïðè âûçîâå X502_OutCycleStop() îæèäàíèå çàâåðøåíèÿ ìîæåò èñïîëüçîâàòüñÿ + ïåðåä âûçîâîì X502_StreamsStop(), ÷òîáû óáåäèòñÿ (åñëè ýòî íåîáõîäèìî), ÷òî + ãåíåðàöèÿ áûëà çàâåðøåíà èìåííî íà ïîñëåäíåé òî÷êå çàãðóæåííîãî öèêëè÷åñêîãî + ñèãíàëà. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] done 0, åñëè åñòü íåçàâåðøåííûé çàïðîñ íà óñòàíîâêó èëè îñòàíîâ + öèêëè÷åñêîãî ñèãíàëà, 1 --- â ïðîòèâíîì ñëó÷àå (âêëþ÷àÿ + ñëó÷àé, êîãäà âûäà÷à öèêëè÷åñêîãî ñèãíàëà âîîáùå íå âåäåòñÿ) + @return Êîä îøèáêè. + ****************************************************************************/ +X502_EXPORT(int32_t) X502_OutCycleCheckSetupDone(t_x502_hnd hnd, uint32_t *done); + + + +/***************************************************************************//** + @brief ×òåíèå ôëàãîâ ñòàòóñà âûâîäà + + Ôóíêöèÿ ÷èòàåò çíà÷åíèÿ ôëàãîâ ñòàòóñà ñèíõðîííîãî âûâîäà èç ðåãèñòðà ñòàòóñà. +  ÷àñòíîñòè ïî ôëàãó #X502_OUT_STATUS_FLAG_BUF_WAS_EMPTY ìîæíî ïðîâåðèòü, + íå áûëî ëè îïóñòîøåíèÿ áóôåðà ñ ìîìåíòà çàïóñêà ñèíõðîííîãî âûâîäà, + ÷òîáû óáåäèòüñÿ, ÷òî íå áûëî ðàçðûâà ñèãíàëà èç-çà íåïîäêà÷åííûõ âîâðåìÿ + äàííûõ. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] status Ôëàãè ñòàòóñà --- íàáîð áèòîâ èç #t_x502_out_status_flags, + îáúåäèíåííûõ ÷åðåç ëîãè÷åñêîå “ÈËÈ”. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_OutGetStatusFlags(t_x502_hnd hnd, uint32_t *status); + + +/***************************************************************************//** + @brief Óñòàíîâêà ðàçìåðà áóôåðà äëÿ ñèíõðîííîãî ââîäà èëè âûâîäà + + Ôóíêöèÿ óñòàíàâëèâàåò ðàçìåð áóôåðà, êîòîðûé èñïîëüçóåòñÿ äëÿ âðåìåííîãî + õðàíåíèÿ äàííûõ íà ïðèåì èëè íà ïåðåäà÷ó. + Ïðåäíàçíà÷åíà äëÿ ñëó÷àåâ, êîãäà ïîëüçîâàòåëÿ ïî êàêèì-ëèáî ïðè÷èíàì íå + óäîâëåòâîðÿåò ðàññ÷èòûâàåìîå áèáëèîòåêîé çíà÷åíèå ïî óìîë÷àíèþ. + + Åñëè ôóíêöèåé X502_SetStreamBufSize áûë óñòàíîâëåí íå íóëåâîé ðàçìåð áóôåðà, + òî ýòîò ðàçìåð áóäåò èñïîëüçîâàòüñÿ íà÷èíàÿ ñ ïåðâîãî âûçîâà X502_StreamsStart èëè + X502_StreamsEnable è ïåðåñ÷èòûâàòüñÿ äàëåå íå áóäåò, ÷òîáû áèáëèîòåêà ñàìà + ðàññ÷èòàëà ðàçìåð áóôåðà íàäî âûçâàòü X502_SetStreamBufSize ñ íóëåâûì ðàçìåðîì + è îí ðàññ÷èòàåòñÿ ïðè ïåðâîì X502_StreamsStart èëè X502_StreamsEnable. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] ch Îïðåäåëÿåò, óñòàíàâëèâàåòñÿ ðàçìåð áóôåðà íà ââîä + èëè íà âûâîä (çíà÷åíèå èç #t_x502_stream_ch). + @param[in] size Ðàçìåð áóôåðà â 32-áèòíûõ îòñ÷åòàõ, + 0 - áèáëèîòåêà áóäåò ñàìà ðàññ÷èòûâàòü çíà÷åíèå ïî óìîë÷àíèþ + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_SetStreamBufSize(t_x502_hnd hnd, uint32_t ch, uint32_t size); + +/***************************************************************************//** + @brief Óñòàíîâêà øàãà ïðè ïåðåäà÷å ïîòîêà íà ââîä èëè âûâîä + + Ôóíêöèÿ óñòàíàâëèâàåò øàã ïåðåäà÷è äàííûõ (øàã ãåíåðàöèè ïðåðûâàíèé + äëÿ PCI-Express èëè ðàçìåð çàïðîñà äëÿ USB) ïðè ïåðåäà÷å ñèíõðîííîãî + ïîòîêà äàííûõ íà ââîä èëè íà âûâîä. + Äàííàÿ ôóíêöèÿ ïðåäíàçíà÷åíà äëÿ ïîëüçîâàòåëåé, êîòîðûõ íå óñòðîèò + àâòîìàòè÷åñêè ðàññ÷èòûâàåìîå áèáëèîòåêîé çíà÷åíèå. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] dma_ch Îïðåäåëÿåò, øàã ïåðåäà÷è óñòàíàâëèâàåòñÿ íà ââîä + èëè íà âûâîä (çíà÷åíèå èç #t_x502_stream_ch). + @param[in] step Øàã ïðåðûâàíèÿ â 32-áèòíûõ îòñ÷åòàõ + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_SetStreamStep(t_x502_hnd hnd, uint32_t dma_ch, uint32_t step); + +/** @} */ + + + +/***************************************************************************//** + @addtogroup func_dsp Ôóíêöèè äëÿ ðàáîòû ñ ñèãíàëüíûì ïðîöåññîðîì + @{ +*******************************************************************************/ +/***************************************************************************//** + @brief Çàãðóçêà ïðîøèâêè ñèãíàëüíîãî ïðîöåññîðà BlackFin + + Ôóíêöèÿ çàãðóæàåò ïðîøèâêó ñèãíàëüíîãî ïðîöåññîðà èç óêàçàííîãî ôàéëà â + ïðîöåññîð è çàïóñêàåò åå, ïðîâåðÿåò ïðàâèëüíîñòü çàãðóçêè ïóòåì ïîëó÷åíèÿ + âåðñèè ïðîøèâêè (÷åðåç ñïåöèàëüíóþ êîìàíäó). + Ïðîøèâêà äîëæíà áûòü â áèíàðíîì ôîðìàòå LDR. + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] filename Èìÿ ôàéëà ñ çàãðóæàåìîé ïðîøèâêîé. + @return Êîä îøèáêè. + *****************************************************************************/ +X502_EXPORT(int32_t) X502_BfLoadFirmware(t_x502_hnd hnd, const char* filename); + + + +/***************************************************************************//** + @brief Ïðîâåðêà, çàãðóæåíà ëè ïðîøèâêà BlackFin + + Ôóíêöèÿ ïåðåäàåò êîìàíäû ïðîöåññîðó BlackFin äëÿ ïîëó÷åíèÿ âåðñèè ïðîøèâêè è + åå ñîñòîÿíèÿ. Óñïåøíîå âûïîëíåíèå êîìàíä ñâèäåòåëüñòâóåò î òîì, ÷òî â + BlackFin çàãðóæåíà äåéñòâèòåëüíàÿ ïðîøèâêà. + Êðîìå òîãî ïðîøèâêå ïåðåäàåòñÿ èíôîðìàöèÿ î ìîäóëå (íàëè÷èå îïöèé, âåðñèÿ + ÏËÈÑ è ò.ä.) äëÿ âíóòðåííåãî èñïîëüçîâàíèÿ. +  ñëó÷àå óñïåõà ìîäóëü ïåðåâîäèòñÿ â ðåæèì DSP. + + Äàííàÿ ôóíêöèÿ ìîæåò ñëóæèòü äëÿ ïðîâåðêè, áûëà ëè çàãðóæåíà ïðîøèâêà ðàíüøå + (÷òîáû íå çàãðóæàòü ïîâòîðíî) èëè äëÿ ïðîâåðêè áûëà ëè îíà çàãðóæåíà ÷åðåç + JTAG-èíòåðôåéñ. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] version Åñëè óêàçàòåëü íå íóëåâîé, òî â äàííîé ïåðåìåííîé + âîçâðàùàåòñÿ âåðñèÿ ïðîøèâêè BlackFin â ñëó÷àå + óñïåøíîé ïðîâåðêè. + @return Êîä îøèáêè. + *****************************************************************************/ +X502_EXPORT(int32_t) X502_BfCheckFirmwareIsLoaded(t_x502_hnd hnd, uint32_t *version); + +/***************************************************************************//** + @brief ×òåíèå áëîêà äàííûõ èç ïàìÿòè ñèãíàëüíîãî ïðîöåññîðà + + Ôóíêöèÿ ñ÷èòûâàåò áëîê äàííûõ íàïðÿìóþ èç ïàìÿòè ïðîöåññîðà. Ìîæåò áûòü + ïðî÷èòàíû äàííûå, êàê èç âíóòðåííåé ïàìÿòè (L1), òàê è èç âíåøíåé SDRAM. + Äëÿ âûïîëíåíèÿ ýòîé ôóíêöèè â BlackFin äîëæíà áûòü çàãðóæåíà åãî ïðîøèâêà. + + Ôóíêöèÿ ïðåäíàçíà÷åíà â ïåðâóþ î÷åðåäü äëÿ ïîëüçîâàòåëåé, ïèøóùèõ ñâîþ + ïðîãðàììó äëÿ ñèãíàëüíîãî ïðîöåññîðà. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] addr Àäðåñ ïàìÿòè, íà÷èíàÿ ñ êîòîðîãî áóäåò ñ÷èòàí áëîê + äàííûõ. + @param[out] regs Ìàññèâ, â êîòîðûé áóäóò ñîõðàíåíî ïðî÷èòàííîå + ñîäåðæèìîå ïàìÿòè. + @param[in] size Êîëè÷åñòâî ñ÷èòûâàåìûõ 32-áèòíûõ ñëîâ. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_BfMemRead(t_x502_hnd hnd, uint32_t addr, uint32_t* regs, + uint32_t size); + +/***************************************************************************//** + @brief Çàïèñü áëîêà äàííûõ â ïàìÿòü ñèãíàëüíîãî ïðîöåññîðà + + Ôóíêöèÿ çàïèñûâàåò áëîê äàííûõ íàïðÿìóþ â ïàìÿòè ïðîöåññîðà BlackFin. Áëîê + äàííûõ äîëæåí áûòü âñåãäà êðàòåí 8 32-áèòíûì ñëîâàì (32 áàéòàì). + Çàïèñü ìîæåò îñóùåñòâëÿòüñÿ êàê âî âíóòðåííþþ ïàìÿòü (L1), òàê è âî âíåøíþþ SDRAM. + Äëÿ âûïîëíåíèÿ ýòîé ôóíêöèè â BlackFin äîëæíà áûòü çàãðóæåíà åãî ïðîøèâêà. + + Ôóíêöèÿ ïðåäíàçíà÷åíà â ïåðâóþ î÷åðåäü äëÿ ïîëüçîâàòåëåé, ïèøóùèõ ñâîþ + ïðîãðàììó äëÿ ñèãíàëüíîãî ïðîöåññîðà. + + @note Ñëåäóåò áûòü îñòîðîæíûì, ò.ê. çàïèñü â îáëàñòü äàííûõ, èñïîëüçóåìóþ + ïðîãðàììîé ìîæåò ïðèâåñòè ê åå íåðàáîòîñïîñîáíîñòè. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] addr Àäðåñ ïàìÿòè, íà÷èíàÿ ñ êîòîðîãî áóäåò çàïèñàí áëîê + äàííûõ. + @param[out] regs Ìàññèâ ñ äàííûìè äëÿ çàïèñè â ñèãíàëüíûé ïðîöåññîð. + @param[in] size Êîëè÷åñòâî çàïèñûâàåìûõ äàííûõ â 32-áèòíûõ ñëîâàõ + (äîëæíî áûòü êðàòíî 8). + @return Êîä îøèáêè. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_BfMemWrite(t_x502_hnd hnd, uint32_t addr, + const uint32_t* regs, uint32_t size); + + +/***************************************************************************//** + @brief Ïåðåäà÷à óïðàâëÿþùåé êîìàíäû ñèãíàëüíîìó ïðîöåññîðó + + Ôóíêöèÿ ïðåäíàçíà÷åíà äëÿ ïåðåäà÷è ïîëüçîâàòåëüñêèõ óïðàâëÿþùèõ êîìàíä + ïðîöåññîðó äëÿ ïîëüçîâàòåëåé, ïèøóùèõ ñâîþ ïðîøèâêó BlackFin. + + Óïðàâëåíèå ðàáîòîé ñèãíàëüíîãî ïðîöåññîðà øòàòíûì îáðàçîì îñóùåñòâëÿåòñÿ + ÷åðåç óïðàâëÿþùèå êîìàíäû, êîòîðûå çàïèñûâàþòñÿ â ñïåöèàëüíóþ îáëàñòü + ïàìÿòè ñèãíàëüíîãî ïðîöåññîðà. Ñèãíàëüíûé ïðîöåññîð îáðàáàòûâàåò êîìàíäó + è ïî çàâåðøåíèþ çàïèñûâàåò â ýòó æå îáëàñòü ðåçóëüòàò. + + Êîìàíäû äåëÿòüñÿ íà ñòàíäàðòíûå, êîòîðûå èñïîëüçóþòñÿ áèáëèîòåêîé x502api è + ðåàëèçîâàíû â øòàòíîé ïðîøèâêå ñèãíàëüíîãî ïðîöåññîðà è ïîëüçîâàòåëüñêèå, + êîòîðûå ïîëüçîâàòåëü ìîæåò îïðåäåëÿòü ïî ñâîåìó óñìîòðåíèþ. + Ïîëüçîâàòåëüñêèå êîìàíäû íà÷èíàþòñÿ ñ êîäà X502_BF_CMD_CODE_USER (0x8000). + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] cmd_code Êîä êîìàíäû - îïðåäåëÿåò, ÷òî çà êîìàíäà âûïîëíÿåòñÿ. + @param[in] par Ïàðàìåòð, ïåðåäàâàåìûé ñ êîìàíäîé (çíà÷åíèå çàâèñèò + îò êîäà êîìàíäû). + @param[in] snd_data Îïöèîíàëüíûå äàííûå, ïåðåäàâàåìûå âìåñòå ñ êîìàíäîé. + Åñëè äàííûå íå ïåðåäàþòñÿ, òî äîëæåí ïåðåäàâàòüñÿ + íóëåâîé óêàçàòåëü è snd_size = 0. + @param[in] snd_size Êîëè÷åñòâî 32-áèòíûõ ñëîâ, ïåðåäàâàåìûõ â snd_data + @param[out] rcv_data Ìàññèâ, â êîòîðûé áóäóò ïåðåäàíû äàííûå, âîçâðàùåííûå + ïðîöåññîðîì ïî çàâåðøåíèþ êîìàíäû. Åñëè äàííûå íå + äîëæíû âîçâðàùàòüñÿ, òî äîëæåí ïåðåäàâàòüñÿ íóëåâîé + óêàçàòåëü, à rcv_size = 0. + @param[in] rcv_size Êîëè÷åñòâî 32-áèòíûõ ñëîâ, êîòîðîå îæèäàåòñÿ, ÷òî + âåðíåò ïðîöåññîð ïî âûïîëíåíèþ êîìàíäû. Ìàññèâ + rcv_data äîëæåí áûòü ðàññ÷èòàí íà äàííîå êîëè÷åñòâî + ñëîâ. + @param[in] tout Òàéìàóò â òå÷åíèè êîòîðîãî áóäåò îæèäàòüñÿ, êîãäà + ïðîöåññîð çàâåðøèò âûïîëíåíèå êîìàíäû. Ôóíêöèÿ + âîçâðàòèò óïðàâëåíèå ëèáî ïî çàâåðøåíèþ êîìàíäû, + ëèáî ïî òàéìàóòó. + @param[out] recvd_size Åñëè íå ÿâëÿåòñÿ íóëåâûì óêàçàòåëåì, òî â ýòó + ïåðåìåííóþ áóäåò ñîõðàíåíî êîëè÷åñòâî 32-áèòíûõ ñëîâ, + êîòîðîå ðåàëüíî âåðíóë ïðîöåññîð ïîñëå âûïîëíåíèÿ + êîìàíäû (ïðîöåññîð èìååò ïðàâî âåðíóòü ìåíüøå äàííûõ, + ÷åì çàïðàøèâàëîñü â rcv_size). + @return Êîä îøèáêè. Åñëè ïðîöåññîð âûïîëíèë êîìàíäó ñ íåíóëåâûì + êîäîì çàâåðøåíèÿ, òî ýòîò êîä è áóäåò âîçâðàùåí + ôóíêöèåé. +*******************************************************************************/ +X502_EXPORT(int32_t) X502_BfExecCmd(t_x502_hnd hnd, uint16_t cmd_code, uint32_t par, + const uint32_t* snd_data, uint32_t snd_size, + uint32_t* rcv_data, uint32_t rcv_size, + uint32_t tout, uint32_t* recvd_size); +/** @} */ + + + + + + +/***************************************************************************//** + @addtogroup func_flash Ôóíêöèè äëÿ ðàáîòû ñ Flash-ïàìÿòüþ ìîäóëÿ + @{ +*******************************************************************************/ + +/***************************************************************************//** + @brief ×òåíèå áëîêà äàííûõ èç Flash-ïàìÿòè + + Ôóíêöèÿ ñ÷èòûâàåò ìàññèâ äàííûõ èç Flash-ïàìÿòè ìîäóëÿ â ìàññèâ, ïåðåäàííûé + ïîëüçîâàòåëåì. Äëÿ ñ÷èòûâàíèÿ íå íóæíî ñïåöèàëüíîå ðàçðåøåíèå - îíî äîñòóïíî + âñåãäà. + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] addr Àäðåñ íà÷àëà áëîêà. + @param[out] data Ìàññèâ, êóäà áóäóò ñîõðàíåíû ñ÷èòàííûå äàííûå + (äîëæåí áûòü íå ìåíüøå size áàéò). + @param[in] size Êîëè÷åñòâî áàéò äëÿ ÷òåíèÿ. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_FlashRead(t_x502_hnd hnd, uint32_t addr, uint8_t* data, + uint32_t size); +/***************************************************************************//** + @brief Çàïèñü áëîêà äàííûõ âî Flash-ïàìÿòü ìîäóëÿ + + Ôóíêöèÿ çàïèñûâàåò ïåðåäàííûé ìàññèâ äàííûõ âî Flash-ïàìÿòü ìîäóëÿ. + Ýòà îáëàñòü äîëæíà áûòü ïðåäâàðèòåëüíî ñòåðòà ñ ïîìîùüþ X502_FlashErase() è + äî íà÷àëà èçìåíåíèÿ äîëæíà áûòü âûçâàíà ôóíêöèÿ X502_FlashWriteEnable(), + ÷òîáû ðàçðåøèòü ëþáîå èçìåíåíèå ñîäåðæèìîãî Flash-ïàìÿòè. + Ïîëüçîâàòåëþ äëÿ çàïèñè äîñòóïíû òîëüêî ïåðâûå #X502_FLASH_USER_SIZE áàéò + Flash-ïàìÿòè. + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] addr Àäðåñ íà÷àëà áëîêà. + @param[in] data Ìàññèâ ñ çàïèñûâàåìûìè äàííûìè (äîëæåí áûòü íå ìåíüøå + size áàéò). + @param[in] size Êîëè÷åñòâî áàéò äëÿ çàïèñè. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_FlashWrite(t_x502_hnd hnd, uint32_t addr, + const uint8_t* data, uint32_t size); +/***************************************************************************//** + @brief Ñòèðàíèå áëîêà âî Flash-ïàìÿòè + + Ôóíêöèÿ ñòèðàåò áëîê âî Flash-ïàìÿòè ìîäóëÿ (âñå ÿ÷åéêè áóäóò ÷èòàòüñÿ êàê + 0xFF). Àäðåñ è ðàçìåð äîëæíû áûòü êðàòíû 4096 áàéò! + Ïåðåä âûçîâîì ýòîé ôóíêöèè äîëæíà áûòü ðàçðåøåíà çàïèñü + â ïîëüçîâàòåëüñêóþ îáëàñòü ñ ïîìîùüþ X502_FlashWriteEnable(). + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] addr Àäðåñ íà÷àëà áëîêà (äîëæåí áûòü êðàòåí 4K). + @param[in] size Êîëè÷åñòâî áàéò äëÿ ñòèðàíèÿ (êðàòíî 4K). + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_FlashErase(t_x502_hnd hnd, uint32_t addr, uint32_t size); +/***************************************************************************//** + @brief Ðàçðåøåíèå çàïèñè â ïîëüçîâàòåëüñêóþ îáëàñòü Flash-ïàìÿòè + + Ôóíêöèÿ ðàçðåøàåò çàïèñü â ïîëüçîâàòåëüñêóþ îáëàñòü Flash-ïàìÿòè (ïåðâûå + #X502_FLASH_USER_SIZE áàéò). Äîëæíà áûòü âûçâàíà äî òîãî, êàê + ìîæíî áóäåò èñïîëüçîâàòü X502_FlashErase() è X502_FlashWrite() äëÿ èçìåíåíèÿ + ñîäåðæèìîãî ïîëüçîâàòåëüñêîé îáëàñòè ïàìÿòè. Ïîñëå çàâåðøåíèÿ èçìåíåíèé + ñëåäóåò âûçâàòü X502_FlashWriteDisable(). + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_FlashWriteEnable(t_x502_hnd hnd); +/***************************************************************************//** + @brief Çàïðåò çàïèñè â ïîëüçîâàòåëüñêóþ îáëàñòü Flash-ïàìÿòè + + Ôóíêöèÿ çàïðåùàåò çàïèñü â ïîëüçîâàòåëüñêóþ îáëàñòü Flash-ïàìÿòè ìîäóëÿ + (ïåðâûå #X502_FLASH_USER_SIZE áàéò). Äîëæíà áûòü âûçâàíà ïîñëå òîãî, êàê + íóæíûå äàííûå â ïîëüçîâàòåëüñêîé îáëàñòè áûëè èçìåíåíû ñ ïîìîùüþ + X502_FlashErase() è X502_FlashWrite(), ÷òîáû çàùèòèòü ïîëüçîâàòåëüñêóþ + îáëàñòü îò ñëó÷àéíîé èçìåíåíèÿ â äàëüíåéøåì. + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_FlashWriteDisable(t_x502_hnd hnd); + +/** @} */ + + + + + +/***************************************************************************//** + @addtogroup func_misc Äîïîëíèòåëüíûå âñïîìîãàòåëüíûå ôóíêöèè + @{ +*******************************************************************************/ +/**************************************************************************//** + @brief Ïîëó÷èòü âåðñèþ áèáëèîòåêè + + Ôóíêöèÿ âîçâðàùàåò âåðñèþ áèáëèîòåêè x502api. + Âåðñèÿ âîçâðàùàåòñÿ â âèäå 32-áèòíîãî ÷èñëà. + Ñòðîêîâîå ïðåäñòàâëåíèå âîçâðàùåííîé âåðñèè --- ÷åòûðå ÷èñëà, + ñòàðøåå ñîîòâåòñòâóåò ñòàðøåìó áàéòó, ìëàäøåå --- ìëàäøåìó. + + Ñòàðøèé áàéò --- ìàæîðíàÿ âåðñèÿ, âòîðîé ïî ñòàðøèíñòâó áàéò --- ìèíîðíàÿ, + òðåòèé --- ðåâèçèÿ, ÷åòâåðòûé --- íîìåð ñáîðêè (íå èñïîëüçóåòñÿ --- âñåãäà 0) + + @return 32-áèòíîå ÷èñëî, ïðåäñòàâëÿþùåå ñîáîé âåðñèþ áèáëèîòåêè + *****************************************************************************/ +X502_EXPORT(uint32_t) X502_GetLibraryVersion(void); + +/***************************************************************************//** + @brief Ïîëó÷åíèå ñòðîêè îá îøèáêå + + Ôóíêöèÿ âîçâðàùàåò ñòðîêó, ñîîòâåòñòâóþùóþ ïåðåäàííîìó êîäó îøèáêè. +  íàñòîÿùåå âðåìÿ âîçâðàùàåòñÿ âñåãäà ðóññêàÿ âåðñèÿ ñòðîêè (âîçìîæíî â + áóäóùåì áóäåò âîçìîæíîñòü ñìåíèòü ÿçûê ãëîáàëüíîé ôóíêöèåé). + + @note Ñëåäóåò ó÷åñòü, ÷òî â ÎÑ Windows ñòðîêà âîçâðàùàåòñÿ â + ñòàíäàðòíîé äëÿ Windows êîäèðîâêå CP1251, â òî âðåìÿ êàê â Linux + èñïîëüçóåòñÿ êîäèðîâêà UTF-8. + @param[in] err Êîä îøèáêè, äëÿ êîòîðîãî íóæíî âåðíóòü ñòðîêó. + @return Óêàçàòåëü íà ñòðîêó, ñîîòâåòñòâóþùóþ êîäó îøèáêè + ******************************************************************************/ +X502_EXPORT(const char*) X502_GetErrorString(int32_t err); + +/***************************************************************************//** + @brief Ìîðãàíèå ñâåòîäèîäîì + + Ïðè âûçîâå ýòîé ôóíêöèè, åñëè íå çàïóùåí ñèíõðîííûé ââîä/âûâîä, ïðîèñõîäèò + êðàòêîâðåìåííîå çàòóõàíèå êðàñíîãî öâåòà ñâåòîäèîäà íà ïåðåäíåé ïàíåëè + L-502 èëè ñâåòîäèîäà LED1 ìîäóëÿ E-502. + Ìîæåò áûòü èñïîëüçîâàíà äëÿ âèçóàëüíîé èäåíòèôèêàöèè ìîäóëÿ ïîñëå åãî + îòêðûòèÿ. + + Ïðè çàïóùåííîì ñèíõðîííîì ââîäå/âûâîäå äàííûé ñâåòîäèîä âñåãäà + ãîðèò çåëåíûì öâåòîì è äàííàÿ ôóíêöèÿ íå âëèÿåò íà åãî ñîñòîÿíèå. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_LedBlink(t_x502_hnd hnd); + + +/***************************************************************************//** + @brief Óñòàíîâêà ïîäòÿãèâàþùèõ ðåçèñòîðîâ íà âõîäíûõ ëèíèÿõ + + Ôóíêöèÿ ìîæåò èñïîëüçîâàòüñÿ äëÿ âêëþ÷åíèÿ ïîäòÿãèâàþùèõ ðåçèñòîðîâ íà + öèôðîâûõ âõîäàõ. Äëÿ ðàçíûõ ìîäóëåé ïîäòÿãèâàþùèå ðåçèñòîðû ðåàëèçîâàíû + íà ðàçíûõ âõîäàõ. Äëÿ ìîäóëåé E-502 è L-502 ìîæíî âêëþ÷àòü èõ íà ëèíèÿõ SYN1 è SYN2. + Äëÿ L-502 ìîæíî îòäåëüíî çàäàâàòü âêëþ÷åíû èëè îòêëþ÷åíû ïîäòÿæêè + íà ìëàäøåé èëè ñòàðøåé ïîëîâèíå öèôðîâûõ ëèíèé. Äëÿ E-502 ìîæíî âêëþ÷èòü + ïîäòÿãèâàþùèå ê íóëþ ðåçèñòîðû íà âõîäàõ ìåæìîäóëüíîé ñèíõðîíèçàöèè. + Äëÿ E-16 ëèíèé TRIG è INT ìîãóò áûòü êàê âõîäîì, òàê è âûõîäîì äëÿ ñèãíàëîâ START è CONV. + + Íà íå óêàçàííûõ ëèíèÿõ ïîäòÿãèâàþùèå ðåçèñòîðû áóäóò îòêëþ÷åíû, + åñëè îíè áûëè âêëþ÷åíû äî ýòîãî. + + Ïðè âêëþ÷åíèè ïèòàíèÿ âñå ïîäòÿãèâàþùèå ðåçèñòîðû îòêëþ÷åíû. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] pullups Ôëàãè (èç #t_x502_pullups), îïðåäåëÿþùèå, íà êàêèõ + ëèíèÿõ âêëþ÷åíû ïîäòÿãèâàþùèå ðåçèñòîðû. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_SetDigInPullup(t_x502_hnd hnd, uint32_t pullups); + +/***************************************************************************//** + @brief Ïîëó÷åíèå ïàðàìåòðà pullups äëÿ ìîäóëÿ + + Ïîëó÷åíèå òåêóùåãî ñîñòîÿíèÿ ïàðàìåòðà pullups èç ìîäóëÿ. Ðåàëèçîâàíî òîëüêî äëÿ E16 + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[out] pullups Ôëàãè (èç #t_x502_pullups), îïðåäåëÿþùèå, íà êàêèõ + ëèíèÿõ âêëþ÷åíû ïîäòÿãèâàþùèå ðåçèñòîðû. + @return Êîä îøèáêè. + ******************************************************************************/ +X502_EXPORT(int32_t) X502_GetDigInPullup(t_x502_hnd hnd, uint32_t *pullups); + +/***************************************************************************//** + @brief Ïðîâåðêà ïîääåðæêè ìîäóëåì çàäàííîé âîçìîæíîñòè + + Ôóíêöèÿ èñïîëüçóåòñÿ äëÿ ïðîâåðêè, ïîääåðæèâàåòñÿ ëè îïðåäåëåííàÿ âîçìîæíîñòü + èç #t_x502_features äëÿ äàííîãî ìîäóëÿ ñ òåêóùèìè âåðñèÿìè ïðîøèâîê. + + Åñëè âîçìîæíîñòü ïîääåðæèâàåòñÿ, òî áóäåò âîçâðàùåí êîä #X502_ERR_OK. + + Äàííàÿ ôóíêöèÿ äîñòóïíà â áèáëèîòåêå âåðñèè 1.1.6 èëè âûøå. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] feature Çíà÷åíèå èç #t_x502_features, îïðåäåëÿþùèå, êàêóþ + âîçìîæíîñòü íåîáõîäèìî ïðîâåðèòü. + @return Åñëè âîçìîæíîñòü ïîääåðæèâàåòñÿ, òî âîçâðàùàåòñÿ + #X502_ERR_OK, èíà÷å --- êîä îøèáêè + ******************************************************************************/ +X502_EXPORT(int32_t) X502_CheckFeature(t_x502_hnd hnd, uint32_t feature); + +/***************************************************************************//** + @brief Ýêñïåðèìåíòàëüíàÿ ôóíêöèÿ. Èãíîðèðîâàòü îøèáêó ñ çàäàííûì íîìåðîì + + Ôóíêöèÿ èñïîëüçóåòñÿ äëÿ óñòàíîâêè íîìåðà îøèáêè, êîòîðóþ API áóäåò èãíîðèðîâàòü + è ïðîäîëæèò ðàáîòó êàê åñëè áû ýòîé îøèáêè íå áûëî. + Äàííàÿ ôóíêöèÿ áóäåò ïîëåçíà äëÿ òåñòîâ ñàìîãî API èëè ýêñïåðèìåíòîâ ñ íèì. + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] err Çíà÷åíèå èç #t_x502_errs, êàêóþ îøèáêó èãíîðèðîâàòü. + Ðåàëèçîâàíî òîëüêî äëÿ X502_ERR_STREAM_IS_RUNNING + + @return Êîä îøèáêè + ******************************************************************************/ +X502_EXPORT(int32_t) X502_SuppressError(t_x502_hnd hnd, int32_t err); + + +X502_EXPORT(int32_t) X502_SetAfcCoef(t_x502_hnd hnd, double Fd, double SigFreq, double *K, uint32_t size, int enable); +X502_EXPORT(int32_t) X502_GetAfcCoef(t_x502_hnd hnd, double *Fd, double *SigFreq, double *K, uint32_t size, int *enable); + + +/***************************************************************************//** + @brief Ôóíêöèÿ çàäàíèÿ ïàðàìåòðîâ ìîäóëÿ + + Ôóíêöèÿ èñïîëüçóåòñÿ äëÿ óñòàíîâêè ïàðàìåòðîâ ìîäóëÿ +  íîâûõ âåðñèÿõ api ïàðàìåòðû ìîãóò äîáàâëÿòüñÿ + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] param Çíà÷åíèå èç #t_x502_params, íîìåð óñòàíàâëèâàåìîãî ïàðàìåòðà + @param[in] value Çíà÷åíèå ïàðàìåòðà + + @return Êîä îøèáêè + ******************************************************************************/ +X502_EXPORT(int32_t) X502_SetParam(t_x502_hnd hnd, uint32_t param, void *value); + +/***************************************************************************//** + @brief Ôóíêöèÿ çàäàíèÿ ïàðàìåòðîâ öèôðîâîãî ââîäà äëÿ ìîäóëÿ LTA37 + + @param[in] hnd Îïèñàòåëü ìîäóëÿ. + @param[in] echo_en Âêëþ÷åíèå ðåæèìà ýõî êàíàëà, äàííûå íà ââîäå - êîä âûâîäà íà ýõî êàíàë, èíà÷å ââîä öèôðîâûõ âõîäîâ DI + @param[in] echo_ch Íîìåð êàíàëà èç äàííûõ êîòîðîãî áåðóòñÿ ýõî äàííûå + @param[in] di_ch_en_mask Ìàñêà âêëþ÷åííûõ êàíàëîâ â ïîòîêå ââîäà + + @return Êîä îøèáêè + ******************************************************************************/ +X502_EXPORT(int32_t) LTA37_SetDIParam(t_x502_hnd hnd, uint32_t echo_en, uint32_t echo_ch, uint32_t di_ch_en_mask); + +/** @} */ +/** @} */ + +#ifdef __cplusplus +} +#endif + + +#endif // X502API_H diff --git a/python_app/gui/controllers/app_window_config/profile_io_mixin.py b/python_app/gui/controllers/app_window_config/profile_io_mixin.py index f88926e..da398f9 100644 --- a/python_app/gui/controllers/app_window_config/profile_io_mixin.py +++ b/python_app/gui/controllers/app_window_config/profile_io_mixin.py @@ -419,6 +419,7 @@ class AppWindowConfigProfileIOMixin: self._set_combo_current_text(self._processing_mode, gui_state.processing.selected_mode) self._show_magnitude_checkbox.setChecked(bool(gui_state.processing.pass_through.show_magnitude)) self._show_phase_checkbox.setChecked(bool(gui_state.processing.pass_through.show_phase)) + self._unwrap_phase_checkbox.setChecked(bool(gui_state.processing.pass_through.unwrap_phase)) self._pass_through_combo_filter_input.setText(str(gui_state.processing.pass_through.combo_filter)) self._pass_through_fixed_y_enabled.setChecked(bool(gui_state.processing.pass_through.fixed_y_enabled)) self._pass_through_y_min_db.setValue(float(gui_state.processing.pass_through.y_min_db)) diff --git a/python_app/gui/controllers/app_window_config/state_builders.py b/python_app/gui/controllers/app_window_config/state_builders.py index 2b7c460..b3e7c4a 100644 --- a/python_app/gui/controllers/app_window_config/state_builders.py +++ b/python_app/gui/controllers/app_window_config/state_builders.py @@ -203,6 +203,7 @@ class AppWindowConfigStateBuildersMixin: pass_through=GuiPassThroughStateModel( show_magnitude=True, show_phase=True, + unwrap_phase=False, combo_filter="", fixed_y_enabled=False, y_min_db=-100.0, @@ -323,6 +324,7 @@ class AppWindowConfigStateBuildersMixin: pass_through=GuiPassThroughStateModel( show_magnitude=bool(self._show_magnitude_checkbox.isChecked()), show_phase=bool(self._show_phase_checkbox.isChecked()), + unwrap_phase=bool(self._unwrap_phase_checkbox.isChecked()), combo_filter=self._pass_through_combo_filter_input.text().strip(), fixed_y_enabled=bool(self._pass_through_fixed_y_enabled.isChecked()), y_min_db=float(self._pass_through_y_min_db.value()), diff --git a/python_app/gui/controllers/app_window_pipeline_mixin.py b/python_app/gui/controllers/app_window_pipeline_mixin.py index 2806cbf..d672713 100644 --- a/python_app/gui/controllers/app_window_pipeline_mixin.py +++ b/python_app/gui/controllers/app_window_pipeline_mixin.py @@ -12,7 +12,7 @@ from python_app.gui.runtime.history import ( build_run_history_signature, record_result_history, ) -from python_app.hardware_full.kamil_adc_service import apply_kamil_adc_laser_control +from python_app.hardware_full.kamil_adc import apply_kamil_adc_laser_control from python_app.hardware_full.single_radar_service import create_single_radar_service from python_app.models.dataset_model import ComboKey, ResultCollection, SweepCollection from python_app.models.run_config_model import RunConfigModel diff --git a/python_app/gui/controllers/app_window_plot/trace_plot_mixin.py b/python_app/gui/controllers/app_window_plot/trace_plot_mixin.py index 468c992..93a04c9 100644 --- a/python_app/gui/controllers/app_window_plot/trace_plot_mixin.py +++ b/python_app/gui/controllers/app_window_plot/trace_plot_mixin.py @@ -21,6 +21,10 @@ class AppWindowTracePlotMixin: """Return whether phase curves should be rendered.""" return self._show_phase_checkbox.isChecked() + def _unwrap_phase_enabled(self) -> bool: + """Return whether the phase trace should be unwrapped (cumulative).""" + return self._unwrap_phase_checkbox.isChecked() + def _pass_through_fixed_y_range(self) -> tuple[bool, float, float]: """Return normalized magnitude Y-range override for pass-through mode.""" y_min = float(self._pass_through_y_min_db.value()) @@ -48,13 +52,29 @@ class AppWindowTracePlotMixin: return None def _configure_pass_through_magnitude_axis(self, plot: pg.PlotWidget) -> None: - """Apply pass-through magnitude-axis autorange or fixed Y window.""" + """Apply the magnitude Y-axis: a fixed window or autorange. + + The X axis is set explicitly from the data band by the caller, so this + never re-enables X autorange (which would scan all curves every frame). + """ fixed_y_enabled, y_min, y_max = self._pass_through_fixed_y_range() view_box = plot.getViewBox() view_box.invertY(False) - view_box.enableAutoRange(x=True, y=not fixed_y_enabled) if fixed_y_enabled: + view_box.enableAutoRange(y=False) plot.setYRange(y_min, y_max, padding=0.0) + else: + view_box.enableAutoRange(y=True) + + def _configure_pass_through_phase_axis(self, plot: pg.PlotWidget) -> None: + """Apply the phase Y-axis: fixed ±180° when wrapped, autorange when unwrapped.""" + view_box = plot.getViewBox() + view_box.invertY(False) + if self._unwrap_phase_enabled(): + view_box.enableAutoRange(y=True) + else: + view_box.enableAutoRange(y=False) + plot.setYRange(-180.0, 180.0, padding=0.02) def _on_trace_visibility_changed(self, *_args) -> None: """Redraw pass-through traces when magnitude/phase toggles changed.""" @@ -116,7 +136,6 @@ class AppWindowTracePlotMixin: if show_magnitude: mag_item = magnitude_plot.getPlotItem() - self._configure_pass_through_magnitude_axis(magnitude_plot) mag_item.showAxis("left", show=True) mag_item.showAxis("bottom", show=not show_phase) magnitude_plot.setLabel("left", "Magnitude", units="dB") @@ -126,8 +145,6 @@ class AppWindowTracePlotMixin: if show_phase: phase_item = phase_plot.getPlotItem() - phase_plot.getViewBox().invertY(False) - phase_plot.getViewBox().enableAutoRange(x=True, y=False) phase_item.showAxis("left", show=True) phase_item.showAxis("bottom", show=True) phase_plot.setLabel("left", "Phase", units="deg") @@ -179,16 +196,18 @@ class AppWindowTracePlotMixin: x_max = max(x_max, local_x_max) if show_magnitude: - magnitude_values = 20.0 * np.log10(np.maximum(np.abs(payload.trace), 1e-12)) + mag_x, magnitude_values = self._magnitude_display_arrays( + payload.frequency_hz, payload.trace + ) active_magnitude_keys.add(curve_key) magnitude_curve = self._trace_magnitude_curves.get(curve_key) if magnitude_curve is None: - magnitude_curve = pg.PlotCurveItem(pen=pg.mkPen(color, width=1.4)) + magnitude_curve = pg.PlotCurveItem(pen=pg.mkPen(color, width=1.4), antialias=False) self._trace_magnitude_curves[curve_key] = magnitude_curve magnitude_plot.addItem(magnitude_curve) else: magnitude_curve.setPen(pg.mkPen(color, width=1.4)) - magnitude_curve.setData(payload.frequency_hz, magnitude_values) + magnitude_curve.setData(mag_x, magnitude_values) legend_source_magnitude.setdefault(combo_key, magnitude_curve) has_data = True @@ -196,7 +215,7 @@ class AppWindowTracePlotMixin: active_phase_keys.add(curve_key) phase_curve = self._trace_phase_curves.get(curve_key) if phase_curve is None: - phase_curve = pg.PlotCurveItem(pen=pg.mkPen(color, width=1.2)) + phase_curve = pg.PlotCurveItem(pen=pg.mkPen(color, width=1.2), antialias=False) self._trace_phase_curves[curve_key] = phase_curve phase_plot.addItem(phase_curve) else: @@ -231,16 +250,17 @@ class AppWindowTracePlotMixin: phase_sources=legend_source_phase, ) - if has_data: - if np.isfinite(x_min) and np.isfinite(x_max): - if show_magnitude: - magnitude_plot.setXRange(x_min, x_max, padding=0.02) - if show_phase: - phase_plot.setXRange(x_min, x_max, padding=0.02) - if show_phase: - phase_plot.setYRange(-180.0, 180.0, padding=0.02) + # X is the (constant) frequency band: set it explicitly rather than + # autoranging every frame. Y is configured once per draw. + if has_data and np.isfinite(x_min) and np.isfinite(x_max): if show_magnitude: - self._configure_pass_through_magnitude_axis(magnitude_plot) + magnitude_plot.setXRange(x_min, x_max, padding=0.02) + if show_phase: + phase_plot.setXRange(x_min, x_max, padding=0.02) + if show_magnitude: + self._configure_pass_through_magnitude_axis(magnitude_plot) + if show_phase: + self._configure_pass_through_phase_axis(phase_plot) return has_data @staticmethod @@ -268,15 +288,40 @@ class AppWindowTracePlotMixin: plot.removeItem(curve) cache.clear() - def _phase_display_arrays(self, frequency_hz: np.ndarray, trace: np.ndarray) -> tuple[np.ndarray, np.ndarray]: - """Return phase display arrays with decimation for faster rendering.""" - max_points = int(getattr(self, "_trace_phase_render_max_points", 1200)) - if max_points > 0 and trace.size > max_points: - step = max(1, int(np.ceil(trace.size / max_points))) - frequency_hz = frequency_hz[::step] - trace = trace[::step] - phase_values = np.arctan2(trace.imag, trace.real) * (180.0 / np.pi) - return frequency_hz, phase_values + def _render_decimation_step(self, size: int) -> int: + """Stride that caps a rendered trace at the configured maximum point count. + + Beyond ~1 point per screen pixel there is no visual gain, so decimating + keeps the line plots responsive even with many combos on screen. + """ + max_points = int(getattr(self, "_trace_render_max_points", 800)) + if max_points > 0 and size > max_points: + return max(1, int(np.ceil(size / max_points))) + return 1 + + def _magnitude_display_arrays( + self, frequency_hz: np.ndarray, trace: np.ndarray + ) -> tuple[np.ndarray, np.ndarray]: + """Return decimated (frequency, magnitude-dB) arrays for fast rendering.""" + step = self._render_decimation_step(int(trace.size)) + frequency_hz = frequency_hz[::step] + magnitude_db = 20.0 * np.log10(np.maximum(np.abs(trace[::step]), 1e-12)) + return frequency_hz, magnitude_db + + def _phase_display_arrays( + self, frequency_hz: np.ndarray, trace: np.ndarray + ) -> tuple[np.ndarray, np.ndarray]: + """Return decimated (frequency, phase-deg) arrays, unwrapped when enabled. + + Unwrapping runs at full resolution (before decimation) so the cumulative + phase is correct, then both arrays are decimated together for rendering. + """ + phase = np.angle(trace) + if self._unwrap_phase_enabled(): + phase = np.unwrap(phase) + phase_deg = np.degrees(phase) + step = self._render_decimation_step(int(trace.size)) + return frequency_hz[::step], phase_deg[::step] def _sync_trace_legends( self, @@ -363,26 +408,24 @@ class AppWindowTracePlotMixin: return if show_magnitude: - self._configure_pass_through_magnitude_axis(magnitude_plot) magnitude_plot.getPlotItem().showAxis("bottom", show=not show_phase) magnitude_plot.setLabel("left", "Magnitude", units="dB") magnitude_plot.setTitle(title) if not show_phase: magnitude_plot.setLabel("bottom", "Frequency", units="Hz") if show_phase: - phase_plot.getViewBox().invertY(False) - phase_plot.getViewBox().enableAutoRange(x=True, y=False) phase_plot.getPlotItem().showAxis("bottom", show=True) phase_plot.setLabel("left", "Phase", units="deg") phase_plot.setLabel("bottom", "Frequency", units="Hz") phase_plot.setTitle(title) if show_magnitude: - magnitude_db = 20.0 * np.log10(np.maximum(np.abs(samples), 1e-12)) + mag_x, magnitude_db = self._magnitude_display_arrays(trace.frequency_hz, samples) magnitude_curve = pg.PlotCurveItem( - trace.frequency_hz, + mag_x, magnitude_db, pen=pg.mkPen("#ffd166", width=1.8), + antialias=False, ) magnitude_plot.addItem(magnitude_curve) self._trace_magnitude_curves[ @@ -390,23 +433,26 @@ class AppWindowTracePlotMixin: ] = magnitude_curve if show_phase: - phase_values = np.degrees(np.angle(samples)) + phase_x, phase_values = self._phase_display_arrays(trace.frequency_hz, samples) phase_curve = pg.PlotCurveItem( - trace.frequency_hz, + phase_x, phase_values, pen=pg.mkPen("#80ed99", width=1.4, style=Qt.PenStyle.DashLine), + antialias=False, ) phase_plot.addItem(phase_curve) self._trace_phase_curves[ (int(trace.combo.input), int(trace.combo.output), 0, "__single_trace__") ] = phase_curve - phase_plot.setYRange(-180.0, 180.0, padding=0.02) if np.size(trace.frequency_hz) > 1: x_min = float(np.min(trace.frequency_hz)) x_max = float(np.max(trace.frequency_hz)) if show_magnitude: magnitude_plot.setXRange(x_min, x_max, padding=0.02) - self._configure_pass_through_magnitude_axis(magnitude_plot) if show_phase: phase_plot.setXRange(x_min, x_max, padding=0.02) + if show_magnitude: + self._configure_pass_through_magnitude_axis(magnitude_plot) + if show_phase: + self._configure_pass_through_phase_axis(phase_plot) diff --git a/python_app/gui/controllers/app_window_preprocess_mixin.py b/python_app/gui/controllers/app_window_preprocess_mixin.py index ec435ec..382725b 100644 --- a/python_app/gui/controllers/app_window_preprocess_mixin.py +++ b/python_app/gui/controllers/app_window_preprocess_mixin.py @@ -3,7 +3,6 @@ from __future__ import annotations from python_app.gui.preprocess_dialog import PreprocessDialog -from python_app.hardware_full.kamil_adc_service import KamilAdcService from python_app.orchestration.preprocess_assets import ( PREPROCESS_ASSET_SPECS, VISIBLE_PREPROCESS_ASSET_KEYS, @@ -429,8 +428,8 @@ class AppWindowPreprocessMixin: self._stop_run() pipeline_was_paused = True - point_count = self._read_kamil_adc_point_count(config) - calibration, reference = build_kamil_adc_neutral_s21_sets(config, point_count) + calibration, reference = build_kamil_adc_neutral_s21_sets(config) + point_count = config.radar.kamil_adc.band.points self._store.save_set("s21_calibration", radar_key, set_name, calibration) self._store.save_set("s21_reference", radar_key, set_name, reference) @@ -454,25 +453,6 @@ class AppWindowPreprocessMixin: if pipeline_was_paused: self._start_run() - def _read_kamil_adc_point_count(self, config) -> int: - """Read one Kamil ADC sweep and return its actual point count.""" - dialog = self._ensure_preprocess_dialog() - dialog.set_status("Reading one Kamil ADC sweep to detect point count...") - self._log("Reading one Kamil ADC sweep to detect neutral-set point count") - - radar = KamilAdcService(config) - try: - radar.open() - radar.configure(config.radar.sweep) - sweep = radar.acquire() - finally: - radar.close() - - point_count = int(sweep.x.size) - if point_count <= 0: - raise RuntimeError("Kamil ADC returned an empty sweep while detecting point count") - return point_count - def _build_single_radar_capture_session( self, *, diff --git a/python_app/gui/controllers/app_window_ui_mixin.py b/python_app/gui/controllers/app_window_ui_mixin.py index fe8dc22..f199a11 100644 --- a/python_app/gui/controllers/app_window_ui_mixin.py +++ b/python_app/gui/controllers/app_window_ui_mixin.py @@ -125,7 +125,10 @@ class AppWindowUiMixin: self._trace_phase_legend_combo_keys = set() self._trace_magnitude_curves = {} self._trace_phase_curves = {} - self._trace_phase_render_max_points = 400 + # Cap rendered points per trace (magnitude and phase alike): beyond ~1 + # point/pixel there is no visual gain, and decimation keeps pass-through + # responsive with many combos on screen. + self._trace_render_max_points = 800 self._plot_stack.addWidget(self._trace_plots_container) diff --git a/python_app/gui/controllers/sections/processing_section.py b/python_app/gui/controllers/sections/processing_section.py index 232b6c9..19f1da5 100644 --- a/python_app/gui/controllers/sections/processing_section.py +++ b/python_app/gui/controllers/sections/processing_section.py @@ -80,6 +80,13 @@ def build_processing_group(owner) -> QGroupBox: owner._show_phase_checkbox = QCheckBox("Show phase") owner._show_phase_checkbox.setChecked(bool(pass_defaults.show_phase)) + owner._unwrap_phase_checkbox = QCheckBox("Unwrap phase") + owner._unwrap_phase_checkbox.setToolTip( + "Plot the cumulative (unwrapped) phase instead of wrapping to ±180°; " + "the phase axis auto-scales to the unwrapped range." + ) + owner._unwrap_phase_checkbox.setChecked(bool(pass_defaults.unwrap_phase)) + owner._pass_through_combo_filter_input = QLineEdit(str(pass_defaults.combo_filter)) owner._pass_through_combo_filter_input.setPlaceholderText("empty = all, e.g. 0:0,1:0") @@ -105,12 +112,13 @@ def build_processing_group(owner) -> QGroupBox: [ owner._show_magnitude_checkbox, owner._show_phase_checkbox, + owner._unwrap_phase_checkbox, ("Switch combos", owner._pass_through_combo_filter_input), owner._pass_through_fixed_y_enabled, ("Y min dB", owner._pass_through_y_min_db), ("Y max dB", owner._pass_through_y_max_db), ], - split_index=4, + split_index=5, ) owner._processing_mode_pages.addWidget(pass_through_page) @@ -492,6 +500,7 @@ def build_processing_group(owner) -> QGroupBox: owner._processing_mode.currentTextChanged.connect(owner._on_processing_mode_changed) owner._show_magnitude_checkbox.toggled.connect(owner._on_trace_visibility_changed) owner._show_phase_checkbox.toggled.connect(owner._on_trace_visibility_changed) + owner._unwrap_phase_checkbox.toggled.connect(owner._on_trace_visibility_changed) owner._pass_through_combo_filter_input.editingFinished.connect(owner._on_trace_visibility_changed) owner._pass_through_fixed_y_enabled.toggled.connect(owner._sync_pass_through_y_controls) owner._pass_through_fixed_y_enabled.toggled.connect(owner._on_processing_live_settings_changed) diff --git a/python_app/hardware_full/kamil_adc/__init__.py b/python_app/hardware_full/kamil_adc/__init__.py new file mode 100644 index 0000000..414caaa --- /dev/null +++ b/python_app/hardware_full/kamil_adc/__init__.py @@ -0,0 +1,36 @@ +"""Kamil ADC reference-channel acquisition. + +Layered into small, single-responsibility modules: + +* :mod:`~python_app.hardware_full.kamil_adc.protocol` — TTY wire format → aligned + :class:`RawSweep` (pure, incremental parser). +* :mod:`~python_app.hardware_full.kamil_adc.processing` — reference-phase + frequency axis, amplitude normalization, crop + resample to a fixed grid (pure). +* :mod:`~python_app.hardware_full.kamil_adc.tty_reader` — background thread that + drains the TTY and publishes the latest sweep. +* :mod:`~python_app.hardware_full.kamil_adc.service` — collector process lifecycle + and ``acquire() -> SweepResult``. +* :mod:`~python_app.hardware_full.kamil_adc.laser` — pre-acquisition laser setup. +""" + +from python_app.hardware_full.kamil_adc.laser import apply_kamil_adc_laser_control +from python_app.hardware_full.kamil_adc.processing import ( + KamilAdcProcessingParams, + KamilAdcSweepProcessor, +) +from python_app.hardware_full.kamil_adc.protocol import ( + KamilAdcStreamParser, + RawSweep, +) +from python_app.hardware_full.kamil_adc.service import KamilAdcService +from python_app.hardware_full.kamil_adc.tty_reader import KamilAdcTtyReader + +__all__ = [ + "KamilAdcProcessingParams", + "KamilAdcService", + "KamilAdcStreamParser", + "KamilAdcSweepProcessor", + "KamilAdcTtyReader", + "RawSweep", + "apply_kamil_adc_laser_control", +] diff --git a/python_app/hardware_full/kamil_adc/laser.py b/python_app/hardware_full/kamil_adc/laser.py new file mode 100644 index 0000000..3ee22d5 --- /dev/null +++ b/python_app/hardware_full/kamil_adc/laser.py @@ -0,0 +1,103 @@ +"""Laser-controller setup applied before Kamil ADC acquisition. + +This mirrors the legacy ``device_main`` command sequence: connect to the laser +controller, reset it, and apply either manual or variation mode per +``radar.laser_control``. The wire protocol is unchanged from the standalone tool; +only its home moved into the project. +""" + +from __future__ import annotations + +import logging + +from python_app.models.run_config_model import RunConfigModel + +logger = logging.getLogger(__name__) + + +def apply_kamil_adc_laser_control(config: RunConfigModel) -> bool: + """Apply configured laser settings via the device_main command sequence. + + Returns ``True`` when settings were applied, ``False`` when laser control is + disabled. The controller is always disconnected before returning. + """ + laser = config.radar.laser_control + if not laser.enabled: + logger.debug("Kamil ADC laser control disabled; skipping") + return False + + _validate_laser_control_config(config) + + from python_app.hardware_full.laser_control.controller import ( + DEVICE_MAIN_MESSAGE_ID, + LaserController, + ) + from python_app.hardware_full.laser_control.models import VariationType + + controller = LaserController( + port=laser.port, + pi_coeff1_p=laser.pi_coeff1_p, + pi_coeff1_i=laser.pi_coeff1_i, + pi_coeff2_p=laser.pi_coeff2_p, + pi_coeff2_i=laser.pi_coeff2_i, + ) + try: + controller.connect() + controller.reset() + mode = laser.mode.strip().lower() + logger.info("Applying Kamil ADC laser control in %s mode", mode) + if mode == "manual": + manual = laser.manual + controller.set_manual_mode( + temp1=manual.temp1, + temp2=manual.temp2, + current1=manual.current1, + current2=manual.current2, + message_id=DEVICE_MAIN_MESSAGE_ID, + ) + return True + if mode == "variation": + variation = laser.variation + try: + variation_type = VariationType[variation.variation_type] + except KeyError as exc: + raise ValueError( + f"Unsupported radar.laser_control.variation.variation_type: {variation.variation_type}" + ) from exc + + controller.set_manual_mode( + temp1=variation.static_temp1, + temp2=variation.static_temp2, + current1=variation.static_current1, + current2=variation.static_current2, + message_id=DEVICE_MAIN_MESSAGE_ID, + ) + controller.start_variation( + variation_type=variation_type, + params={ + "static_temp1": variation.static_temp1, + "static_temp2": variation.static_temp2, + "static_current1": variation.static_current1, + "static_current2": variation.static_current2, + "min_value": variation.min_value, + "max_value": variation.max_value, + "step": variation.step, + "time_step": variation.time_step, + "delay_time": variation.delay_time, + }, + ) + return True + raise RuntimeError(f"Unsupported laser_control mode: {laser.mode}") + finally: + controller.disconnect() + + +def _validate_laser_control_config(config: RunConfigModel) -> None: + laser = config.radar.laser_control + if not laser.port: + raise ValueError("radar.laser_control.port is required when laser_control is enabled") + mode = laser.mode.strip().lower() + if mode not in {"manual", "variation"}: + raise ValueError("radar.laser_control.mode must be 'manual' or 'variation'") + if mode == "variation" and not laser.variation.variation_type: + raise ValueError("radar.laser_control.variation.variation_type is required") diff --git a/python_app/hardware_full/kamil_adc/processing.py b/python_app/hardware_full/kamil_adc/processing.py new file mode 100644 index 0000000..2180fe4 --- /dev/null +++ b/python_app/hardware_full/kamil_adc/processing.py @@ -0,0 +1,185 @@ +"""Signal processing for the Kamil ADC reference-channel acquisition. + +Each sweep arrives as two aligned complex arrays — the *main* signal and a +*reference* signal — sampled at the same step indices (see +:mod:`python_app.hardware_full.kamil_adc.protocol`). Turning that into a stable, +comparable S21 trace is a fixed three-stage pipeline: + +1. **Frequency axis from the reference phase.** The reference arm has a constant + electrical delay, so its unwrapped phase is an affine function of frequency. + Two fixed calibration anchors ``(phase0, freq0)`` and ``(phase1, freq1)`` — + supplied by config, *never* derived from the live sweep — define that law:: + + f(phase) = freq0 + (phase - phase0) * (freq1 - freq0) / (phase1 - phase0) + + Trigger jitter shifts every sample's absolute phase together, so the measured + band floats from sweep to sweep around the fixed calibration. + +2. **Amplitude normalization.** ``S = main / |reference|`` divides out the + stimulus amplitude. Only the magnitude is removed; the reference phase is used + solely for the axis above. + +3. **Crop + resample onto a fixed grid.** The floating per-sweep axis is resampled + (linear, on real and imaginary parts) onto a single hardcoded grid + ``linspace(band_start, band_stop, band_points)``. Every sweep then shares a + byte-identical frequency axis, so traces can be averaged and subtracted. A + sweep whose floated range does not fully span the band is *rejected* rather + than edge-extrapolated. + +Everything here is pure and free of I/O so it can be unit-tested in isolation. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np + +from python_app.models.run_config_schema import KamilAdcModel + +# A reference sample whose magnitude falls to (effectively) zero carries no usable +# amplitude or phase; such points are dropped before normalization rather than +# producing a division blow-up. The threshold only guards genuine zeros — real +# reference frames are emitted from settled measurements and sit far above it. +_REFERENCE_AMPLITUDE_FLOOR = 1e-9 + +# Linear interpolation onto the band needs at least two distinct source samples; +# a sweep yielding fewer usable points is malformed and rejected. +_MIN_USABLE_POINTS = 2 + + +@dataclass(frozen=True, slots=True) +class KamilAdcProcessingParams: + """Immutable phase→frequency calibration and output-grid definition. + + Built from :class:`~python_app.models.run_config_schema.KamilAdcModel` via + :meth:`from_kamil_model`, but kept decoupled from the config types so the + processing can be exercised with plain numbers in tests. + """ + + phase0_rad: float + freq0_hz: float + phase1_rad: float + freq1_hz: float + band_start_hz: float + band_stop_hz: float + band_points: int + reference_amplitude_floor: float = _REFERENCE_AMPLITUDE_FLOOR + + def __post_init__(self) -> None: + if not np.isfinite([self.phase0_rad, self.phase1_rad, self.freq0_hz, self.freq1_hz]).all(): + raise ValueError("Kamil ADC phase calibration anchors must be finite") + if self.phase0_rad == self.phase1_rad: + raise ValueError("Kamil ADC phase calibration anchors must use distinct phases") + if self.freq0_hz == self.freq1_hz: + raise ValueError("Kamil ADC phase calibration anchors must map to distinct frequencies") + if not (np.isfinite(self.band_start_hz) and np.isfinite(self.band_stop_hz)): + raise ValueError("Kamil ADC band edges must be finite") + if self.band_stop_hz <= self.band_start_hz: + raise ValueError("Kamil ADC band stop_hz must be greater than start_hz") + if self.band_points < 2: + raise ValueError("Kamil ADC band points must be >= 2") + if self.reference_amplitude_floor <= 0.0: + raise ValueError("Kamil ADC reference amplitude floor must be > 0") + + @classmethod + def from_kamil_model(cls, kamil_adc: KamilAdcModel) -> KamilAdcProcessingParams: + """Build parameters from the ``radar.kamil_adc`` config section.""" + calibration = kamil_adc.phase_calibration + band = kamil_adc.band + return cls( + phase0_rad=float(calibration.phase0_rad), + freq0_hz=float(calibration.freq0_hz), + phase1_rad=float(calibration.phase1_rad), + freq1_hz=float(calibration.freq1_hz), + band_start_hz=float(band.start_hz), + band_stop_hz=float(band.stop_hz), + band_points=int(band.points), + ) + + @property + def hz_per_rad(self) -> float: + """Frequency change per radian of reference phase (the calibration slope).""" + return (self.freq1_hz - self.freq0_hz) / (self.phase1_rad - self.phase0_rad) + + +class KamilAdcSweepProcessor: + """Turns aligned (main, reference) sweeps into S21 traces on a fixed grid. + + The output grid is computed once from the parameters and reused for every + sweep, so all traces this processor emits share one identical frequency axis. + """ + + __slots__ = ("_params", "_grid_hz") + + def __init__(self, params: KamilAdcProcessingParams) -> None: + self._params = params + self._grid_hz = np.linspace( + params.band_start_hz, params.band_stop_hz, params.band_points, dtype=np.float64 + ) + + @property + def params(self) -> KamilAdcProcessingParams: + return self._params + + @property + def grid_hz(self) -> np.ndarray: + """The fixed output frequency axis (float32), identical for every sweep.""" + return self._grid_hz.astype(np.float32) + + def reference_frequency_axis(self, reference: np.ndarray) -> np.ndarray: + """Map a reference signal's absolute unwrapped phase to frequency (Hz). + + Returns frequencies in *step order* (not sorted); see the module docstring + for the calibration law. + """ + phase = np.unwrap(np.angle(np.asarray(reference))) + return self._params.freq0_hz + (phase - self._params.phase0_rad) * self._params.hz_per_rad + + def process(self, main: np.ndarray, reference: np.ndarray) -> np.ndarray | None: + """Return the S21 trace resampled onto the fixed grid, or ``None`` to reject. + + ``main`` and ``reference`` are equal-length complex arrays ordered by + ascending step index. ``None`` is returned when the sweep is malformed + (too few usable points) or does not fully cover the configured band. + """ + main = np.asarray(main, dtype=np.complex128) + reference = np.asarray(reference, dtype=np.complex128) + if main.size < _MIN_USABLE_POINTS or main.size != reference.size: + return None + + # Frequency axis from the absolute unwrapped reference phase (step order). + freqs = self.reference_frequency_axis(reference) + + # Amplitude-only normalization; drop points where the reference vanished. + reference_amplitude = np.abs(reference) + with np.errstate(divide="ignore", invalid="ignore"): + s21 = main / reference_amplitude + usable = ( + (reference_amplitude > self._params.reference_amplitude_floor) + & np.isfinite(freqs) + & np.isfinite(s21.real) + & np.isfinite(s21.imag) + ) + if int(np.count_nonzero(usable)) < _MIN_USABLE_POINTS: + return None + + freqs = freqs[usable] + s21 = s21[usable] + + # Sort onto a monotonically increasing axis (handles either sweep + # direction) so interpolation and the coverage check are well defined. + order = np.argsort(freqs, kind="stable") + freqs = freqs[order] + s21 = s21[order] + + # Reject sweeps that do not span the whole band: interpolating past the + # measured edge would inject flat, non-physical points. + if freqs[0] > self._params.band_start_hz or freqs[-1] < self._params.band_stop_hz: + return None + + # Linear interpolation on real/imaginary parts. Because the band lies + # within [freqs[0], freqs[-1]], np.interp never extrapolates here. + real = np.interp(self._grid_hz, freqs, s21.real) + imag = np.interp(self._grid_hz, freqs, s21.imag) + return (real + 1j * imag).astype(np.complex64) diff --git a/python_app/hardware_full/kamil_adc/protocol.py b/python_app/hardware_full/kamil_adc/protocol.py new file mode 100644 index 0000000..7415534 --- /dev/null +++ b/python_app/hardware_full/kamil_adc/protocol.py @@ -0,0 +1,141 @@ +"""Wire protocol for the Kamil ADC collector's TTY stream. + +The collector publishes a stream of 8-byte little-endian frames, four 16-bit +words each: ``[marker, step, ch1, ch2]`` (``ch1``/``ch2`` signed). Three frame +kinds appear in ``do8_freq_ref`` mode: + +* **Sweep boundary** — ``marker, 0xFFFF, 0xFFFF, 0xFFFF``. Delimits sweeps. +* **Main point** — ``0x000A, step, I, Q``. One complex main sample at ``step``. +* **Reference point** — ``0x00A8, step, I, Q``. The reference sample paired with + the main sample of the same ``step`` (emitted only where the DI8 loopback + settled, so reference points are sparser than main points). + +:class:`KamilAdcStreamParser` consumes raw bytes incrementally and yields one +:class:`RawSweep` per completed sweep. Main and reference points are aligned by +step index; only steps carrying *both* survive (a step needs its reference for +the frequency axis and its main for the signal). Anything other than the three +known frame kinds is a protocol violation and raises — the reader fails fast and +lets the supervisor relaunch a clean collector rather than silently resyncing. +""" + +from __future__ import annotations + +from dataclasses import dataclass +import struct + +import numpy as np + +# Wire-format constants. +FRAME_BYTES = 8 +MAIN_MARKER = 0x000A +REFERENCE_MARKER = 0x00A8 +_BOUNDARY_STEP = 0xFFFF + +# marker (u16), step (u16), ch1 (i16), ch2 (i16) — point frames carry signed I/Q. +_FRAME = struct.Struct(" int: + return int(self.steps.size) + + +class KamilAdcStreamParser: + """Incremental, frame-aligning parser turning the TTY byte stream into sweeps. + + Usage: call :meth:`feed` with each chunk of bytes; it returns the list of + sweeps completed by that chunk (usually zero or one). The parser is stateful + but holds no I/O and is cheap to unit-test. + """ + + __slots__ = ("_buffer", "_aligned", "_main", "_reference") + + def __init__(self) -> None: + self._buffer = bytearray() + self._aligned = False + self._main: dict[int, complex] = {} + self._reference: dict[int, complex] = {} + + def feed(self, data: bytes) -> list[RawSweep]: + """Append ``data`` and return any sweeps completed by it.""" + self._buffer.extend(data) + if not self._aligned and not self._align(): + return [] + + sweeps: list[RawSweep] = [] + buffer = self._buffer + while len(buffer) >= FRAME_BYTES: + frame = bytes(buffer[:FRAME_BYTES]) + del buffer[:FRAME_BYTES] + if frame[2:] == _BOUNDARY_TAIL: + sweep = self._take_sweep() + if sweep is not None: + sweeps.append(sweep) + continue + marker, step, real, imag = _FRAME.unpack(frame) + if marker == MAIN_MARKER: + self._main[step] = complex(real, imag) + elif marker == REFERENCE_MARKER: + self._reference[step] = complex(real, imag) + else: + raise ValueError( + f"Kamil ADC protocol violation: unexpected frame marker 0x{marker:04x}" + ) + return sweeps + + def reset(self) -> None: + """Drop all buffered state (e.g. after the collector is relaunched).""" + self._buffer.clear() + self._aligned = False + self._main.clear() + self._reference.clear() + + def _align(self) -> bool: + """Discard pre-roll up to and including the first sweep boundary. + + Returns ``True`` once frame-aligned. Keeps a short tail so a boundary + split across two feeds can still be found on the next chunk. + """ + index = self._buffer.find(_START_FRAME) + if index < 0: + if len(self._buffer) >= FRAME_BYTES: + del self._buffer[: -(FRAME_BYTES - 1)] + return False + del self._buffer[: index + FRAME_BYTES] + self._main.clear() + self._reference.clear() + self._aligned = True + return True + + def _take_sweep(self) -> RawSweep | None: + """Assemble the buffered points into a sweep and reset for the next one.""" + shared = sorted(self._main.keys() & self._reference.keys()) + main = self._main + reference = self._reference + self._main = {} + self._reference = {} + if not shared: + return None + return RawSweep( + steps=np.asarray(shared, dtype=np.int32), + main=np.asarray([main[step] for step in shared], dtype=np.complex64), + reference=np.asarray([reference[step] for step in shared], dtype=np.complex64), + ) diff --git a/python_app/hardware_full/kamil_adc/service.py b/python_app/hardware_full/kamil_adc/service.py new file mode 100644 index 0000000..01804a8 --- /dev/null +++ b/python_app/hardware_full/kamil_adc/service.py @@ -0,0 +1,358 @@ +"""Service that launches the Kamil ADC collector and serves processed sweeps. + +Owns the lifecycle of the external collector process and its TTY reader, and maps +each raw (main, reference) sweep to an :class:`SweepResult` on the fixed +processing grid via :class:`KamilAdcSweepProcessor`. +""" + +from __future__ import annotations + +from contextlib import suppress +from dataclasses import dataclass, field +import logging +import os +from pathlib import Path +import signal +import stat +import subprocess +import threading +import time + +import numpy as np + +from python_app.hardware_full.kamil_adc.processing import ( + KamilAdcProcessingParams, + KamilAdcSweepProcessor, +) +from python_app.hardware_full.kamil_adc.protocol import RawSweep +from python_app.hardware_full.kamil_adc.tty_reader import ( + KamilAdcTtyReader, + raise_if_process_exited, +) +from python_app.hardware_full.librevna_driver.models import SweepResult +from python_app.models.run_config_model import RadarSweepModel, RunConfigModel + +logger = logging.getLogger(__name__) + +# Project root, used to resolve relative collector paths (e.g. +# ``build/bin/kamil_adc_collector``) independent of the launching CWD. +_REPO_ROOT = Path(__file__).resolve().parents[3] +# Default home of the proprietary L-Card runtime libraries the collector loads +# via dlopen. Prepended to LD_LIBRARY_PATH unless the config pins it explicitly. +_DEFAULT_LCARD_LIB_DIR = "~/.local/lib" +# Rejected-sweep logging is throttled so a persistently mis-set band/calibration +# does not flood the log: log the first rejection, then every Nth. +_REJECT_LOG_EVERY = 50 +# The collector's graceful X502 teardown can block, so a stop gives it only this +# brief window to release the device cleanly before escalating to SIGKILL. Caps +# the configured stop_timeout_s so a stop can never hang. +_STOP_KILL_GRACE_S = 0.5 + + +@dataclass(slots=True) +class KamilAdcService: + """Launch the external Kamil ADC collector and serve its processed sweeps.""" + + config: RunConfigModel + _process: subprocess.Popen[bytes] | None = field(init=False, default=None, repr=False) + _reader: KamilAdcTtyReader | None = field(init=False, default=None, repr=False) + _processor: KamilAdcSweepProcessor | None = field(init=False, default=None, repr=False) + _rejected_count: int = field(init=False, default=0, repr=False) + + def __post_init__(self) -> None: + self._validate_config() + + @property + def command(self) -> list[str]: + """External collector command, including the generated ``tty:`` argument.""" + adc = self.config.radar.kamil_adc + return [str(self._resolve_executable()), *adc.args, f"tty:{adc.tty_path}"] + + def open(self, *, stop_event: threading.Event | None = None) -> None: + """Launch the collector and start the TTY reader thread. + + An optional ``stop_event`` lets a caller abort the TTY-wait loop promptly + (e.g. on shutdown) instead of blocking for the full startup timeout. + """ + if self._reader is not None: + return + previous_tty_identity = _prepare_tty_path_for_collector(self.config.radar.kamil_adc.tty_path) + try: + self._start_process() + self._wait_for_tty(previous_tty_identity, stop_event=stop_event) + reader = KamilAdcTtyReader(self.config.radar.kamil_adc.tty_path) + reader.open() + self._reader = reader + logger.info("Kamil ADC service opened") + except Exception: + logger.exception("Kamil ADC service failed to open; cleaning up") + self.close() + raise + + def close(self) -> None: + """Stop the TTY reader and the external collector process.""" + logger.debug("Closing Kamil ADC service") + if self._reader is not None: + with suppress(Exception): + self._reader.close() + self._reader = None + self._stop_process() + + def configure(self, sweep: RadarSweepModel) -> None: + """Build the sweep processor from the ``radar.kamil_adc`` calibration/band. + + The generic ``sweep`` argument is accepted for interface parity with the + other radar services but is not used: the Kamil ADC frequency axis comes + from the reference-phase calibration, and the output grid from + ``radar.kamil_adc.band`` — never from the nominal sweep bounds. + """ + self._processor = KamilAdcSweepProcessor( + KamilAdcProcessingParams.from_kamil_model(self.config.radar.kamil_adc) + ) + self._rejected_count = 0 + params = self._processor.params + logger.debug( + "Kamil ADC configured: band %.6g-%.6g Hz, %d points; calibration " + "(%.6g rad -> %.6g Hz, %.6g rad -> %.6g Hz)", + params.band_start_hz, params.band_stop_hz, params.band_points, + params.phase0_rad, params.freq0_hz, params.phase1_rad, params.freq1_hz, + ) + + def read_device_limits(self) -> dict[str, float | int]: + """Kamil ADC has no runtime-readable sweep-limit API.""" + raise RuntimeError("Kamil ADC device limits are not available") + + def acquire(self) -> SweepResult: + """Return the next sweep that covers the band, as S21 on the fixed grid. + + Sweeps whose floated frequency range does not span the configured band are + rejected and the next sweep is read, until one passes or the sweep timeout + elapses (which then surfaces as a :class:`TimeoutError`). + """ + if self._processor is None: + raise RuntimeError("Kamil ADC service is not configured") + if self._reader is None: + raise RuntimeError("Kamil ADC service is not open") + process = self._process + if process is None or process.poll() is not None: + return_code = None if process is None else process.poll() + raise RuntimeError(f"Kamil ADC collector is not running (code={return_code})") + + grid = self._processor.grid_hz + points = int(grid.size) + deadline = time.monotonic() + self.config.radar.kamil_adc.sweep_timeout_s + while True: + remaining_s = deadline - time.monotonic() + if remaining_s <= 0.0: + raise TimeoutError( + "Timed out waiting for a Kamil ADC sweep covering the configured band" + ) + raw = self._reader.read_sweep(timeout_s=remaining_s, process=process) + s21 = self._processor.process(raw.main, raw.reference) + if s21 is not None: + return SweepResult( + x=grid.copy(), + traces={ + "s11": np.zeros(points, dtype=np.complex64), + "s21": s21, + }, + ) + self._log_rejected_sweep(raw) + + def read_raw_sweep(self) -> RawSweep: + """Return the next raw (main, reference) sweep without any processing. + + Bypasses the frequency mapping, normalization, crop and resample of + :meth:`acquire` — intended for calibration tooling that needs the + unprocessed reference samples. Raises if the service is not open. + """ + if self._reader is None: + raise RuntimeError("Kamil ADC service is not open") + return self._reader.read_sweep( + timeout_s=self.config.radar.kamil_adc.sweep_timeout_s, + process=self._process, + ) + + # ------------------------------------------------------------------ + # Process / TTY lifecycle + # ------------------------------------------------------------------ + + def _start_process(self) -> None: + if self._process is not None and self._process.poll() is None: + return + logger.info("Starting Kamil ADC collector: %s", " ".join(self.command)) + self._process = subprocess.Popen( + self.command, + cwd=str(self._resolve_project_dir()), + env=self._build_env(), + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.STDOUT, + start_new_session=True, + ) + + def _stop_process(self) -> None: + process = self._process + self._process = None + if process is None or process.poll() is not None: + return + logger.info("Stopping Kamil ADC collector (pid=%d)", process.pid) + # The collector's graceful X502 teardown can block, so give it only a brief + # window to release the device cleanly, then SIGKILL the whole group hard. + grace_s = min(self.config.radar.kamil_adc.stop_timeout_s, _STOP_KILL_GRACE_S) + with suppress(ProcessLookupError): + os.killpg(process.pid, signal.SIGTERM) + try: + process.wait(timeout=grace_s) + return + except subprocess.TimeoutExpired: + pass + logger.warning("Kamil ADC collector (pid=%d) did not stop in %.1fs; sending SIGKILL", process.pid, grace_s) + with suppress(ProcessLookupError): + os.killpg(process.pid, signal.SIGKILL) + # close() must never raise: a collector wedged in uninterruptible I/O + # (USB D-state in the L-Card driver) may not be reaped within the grace + # window even after SIGKILL. Best-effort wait; the OS reaps it eventually. + with suppress(subprocess.TimeoutExpired): + process.wait(timeout=1.0) + + def _wait_for_tty( + self, + previous_identity: tuple[object, ...] | None, + *, + stop_event: threading.Event | None = None, + ) -> None: + adc = self.config.radar.kamil_adc + deadline = time.monotonic() + adc.startup_timeout_s + while time.monotonic() < deadline: + if stop_event is not None and stop_event.is_set(): + raise RuntimeError("Kamil ADC startup aborted before TTY became available") + raise_if_process_exited(self._process) + identity = _tty_identity(adc.tty_path) + if identity is not None and identity != previous_identity: + return + if stop_event is not None: + if stop_event.wait(0.05): + raise RuntimeError("Kamil ADC startup aborted before TTY became available") + else: + time.sleep(0.05) + raise TimeoutError( + f"Timed out waiting for Kamil ADC TTY `{adc.tty_path}` to be created by the collector" + ) + + # ------------------------------------------------------------------ + # Path / environment resolution + # ------------------------------------------------------------------ + + def _resolve_executable(self) -> Path: + return self._resolve_path(self.config.radar.kamil_adc.executable_path) + + def _resolve_project_dir(self) -> Path: + project_dir = self.config.radar.kamil_adc.project_dir + return self._resolve_path(project_dir) if project_dir else _REPO_ROOT + + @staticmethod + def _resolve_path(path_str: str) -> Path: + """Expand ``~`` and resolve a relative path against the project root.""" + path = Path(path_str).expanduser() + return path if path.is_absolute() else (_REPO_ROOT / path) + + def _build_env(self) -> dict[str, str]: + """Child environment: inherited env + config env, with the L-Card lib path. + + Config ``env`` values are ``~``/``$VAR`` expanded. Unless the config pins + ``LD_LIBRARY_PATH`` itself, the default L-Card library directory is + prepended so the collector's dlopen of libx502api/libe502api succeeds. + """ + adc = self.config.radar.kamil_adc + env = os.environ.copy() + for key, value in adc.env.items(): + env[key] = os.path.expandvars(os.path.expanduser(value)) + if "LD_LIBRARY_PATH" not in adc.env: + lcard_dir = os.path.expanduser(_DEFAULT_LCARD_LIB_DIR) + existing = env.get("LD_LIBRARY_PATH", "") + env["LD_LIBRARY_PATH"] = f"{lcard_dir}{os.pathsep}{existing}" if existing else lcard_dir + return env + + # ------------------------------------------------------------------ + # Validation / diagnostics + # ------------------------------------------------------------------ + + def _validate_config(self) -> None: + if not self.config.is_kamil_adc: + raise RuntimeError("KamilAdcService requires radar.model='kamil_adc'") + if self.config.radar.driver_mode != "native": + raise RuntimeError("Kamil ADC requires radar.driver_mode='native'") + + adc = self.config.radar.kamil_adc + if not adc.executable_path: + raise ValueError("radar.kamil_adc.executable_path is required") + if not adc.tty_path: + raise ValueError("radar.kamil_adc.tty_path is required") + if any(arg.startswith("tty:") for arg in adc.args): + raise ValueError("radar.kamil_adc.args must not contain tty:; use tty_path instead") + for name in ("startup_timeout_s", "sweep_timeout_s", "stop_timeout_s"): + if getattr(adc, name) <= 0.0: + raise ValueError(f"radar.kamil_adc.{name} must be > 0") + + project_dir = self._resolve_project_dir() + if not project_dir.is_dir(): + raise RuntimeError(f"radar.kamil_adc.project_dir is not a directory: {project_dir}") + executable_path = self._resolve_executable() + if not executable_path.is_file(): + raise RuntimeError(f"radar.kamil_adc.executable_path is not a file: {executable_path}") + if not os.access(executable_path, os.X_OK): + raise RuntimeError(f"radar.kamil_adc.executable_path is not executable: {executable_path}") + + # Surface a malformed calibration/band at open time, not mid-acquisition. + KamilAdcProcessingParams.from_kamil_model(adc) + + def _log_rejected_sweep(self, raw) -> None: + """Log a band-coverage rejection (throttled) with the measured span.""" + self._rejected_count += 1 + if self._rejected_count != 1 and self._rejected_count % _REJECT_LOG_EVERY != 0: + return + params = self._processor.params # type: ignore[union-attr] + try: + freqs = self._processor.reference_frequency_axis(raw.reference) # type: ignore[union-attr] + covered = f"[{float(np.min(freqs)):.6g}, {float(np.max(freqs)):.6g}]" + except Exception: # noqa: BLE001 — diagnostics must never raise + covered = "" + logger.warning( + "Kamil ADC sweep rejected (count=%d): covered %s Hz does not span band " + "[%.6g, %.6g] Hz (usable points=%d). Check phase_calibration/band.", + self._rejected_count, covered, params.band_start_hz, params.band_stop_hz, raw.size, + ) + + +def _tty_identity(path: str) -> tuple[object, ...] | None: + try: + if os.path.islink(path): + stat_result = os.lstat(path) + return ( + "link", + os.readlink(path), + int(stat_result.st_dev), + int(stat_result.st_ino), + int(stat_result.st_mtime_ns), + ) + stat_result = os.stat(path) + except FileNotFoundError: + return None + return ( + "node", + int(stat_result.st_dev), + int(stat_result.st_ino), + int(stat_result.st_mtime_ns), + ) + + +def _prepare_tty_path_for_collector(path: str) -> tuple[object, ...] | None: + """Remove a stale generated TTY symlink/file before starting the collector.""" + try: + stat_result = os.lstat(path) + except FileNotFoundError: + return None + if stat.S_ISLNK(stat_result.st_mode) or stat.S_ISREG(stat_result.st_mode): + os.unlink(path) + return None diff --git a/python_app/hardware_full/kamil_adc/tty_reader.py b/python_app/hardware_full/kamil_adc/tty_reader.py new file mode 100644 index 0000000..9b617da --- /dev/null +++ b/python_app/hardware_full/kamil_adc/tty_reader.py @@ -0,0 +1,190 @@ +"""Background TTY reader publishing the latest completed Kamil ADC sweep. + +The collector emits sweeps continuously, faster than callers invoke +:meth:`KamilAdcService.acquire`. A daemon thread drains the device end of the +TTY non-stop, feeds the bytes to a :class:`KamilAdcStreamParser`, and stores the +most recent :class:`RawSweep` in a single-slot mailbox. :meth:`read_sweep` +returns the freshest sweep; if a newer one arrives before the consumer reads, it +overwrites the previous unread value — by design, since consumers always want the +latest data. Parser/stream errors are captured and re-raised on the consumer +thread (fail-fast; the supervisor relaunches a clean collector). +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +import errno +import logging +import os +import select +import subprocess +import threading +import time + +from python_app.hardware_full.kamil_adc.protocol import KamilAdcStreamParser, RawSweep + +logger = logging.getLogger(__name__) + +# Large reads keep up with bursty CDC-ACM/PTY writers without raising the syscall +# rate; 64 KiB matches the typical Linux PTY buffer size. +_READ_CHUNK_BYTES = 65536 +# select() poll interval — short enough to react to close() promptly, long enough +# that idle CPU stays near zero. +_READ_POLL_INTERVAL_S = 0.1 + + +def raise_if_process_exited(process: subprocess.Popen[bytes] | None) -> None: + """Raise if the external collector process has exited.""" + if process is None: + return + return_code = process.poll() + if return_code is not None: + raise RuntimeError(f"Kamil ADC collector exited with code {return_code}") + + +@dataclass(slots=True) +class KamilAdcTtyReader: + """Daemon-thread TTY reader publishing the latest completed sweep.""" + + tty_path: str + _fd: int | None = field(init=False, default=None, repr=False) + _thread: threading.Thread | None = field(init=False, default=None, repr=False) + _stop_event: threading.Event = field(init=False, default_factory=threading.Event, repr=False) + _mailbox_cv: threading.Condition = field(init=False, default_factory=threading.Condition, repr=False) + _latest_sweep: RawSweep | None = field(init=False, default=None, repr=False) + _reader_error: Exception | None = field(init=False, default=None, repr=False) + _published_count: int = field(init=False, default=0, repr=False) + + def open(self) -> None: + """Open the TTY and start the background reader thread.""" + if self._fd is not None: + return + self._fd = os.open(self.tty_path, os.O_RDONLY | os.O_NOCTTY | os.O_NONBLOCK) + self._stop_event.clear() + self._latest_sweep = None + self._reader_error = None + self._published_count = 0 + self._thread = threading.Thread( + target=self._reader_loop, + name=f"kamil-adc-tty-reader[{self.tty_path}]", + daemon=True, + ) + self._thread.start() + logger.info("Kamil ADC TTY reader started on %s", self.tty_path) + + def close(self) -> None: + """Stop the reader thread and close the TTY descriptor.""" + logger.debug("Stopping Kamil ADC TTY reader on %s", self.tty_path) + self._stop_event.set() + with self._mailbox_cv: + self._mailbox_cv.notify_all() + if self._thread is not None: + self._thread.join(timeout=1.0) + if self._thread.is_alive(): + logger.warning("Kamil ADC reader thread did not stop within 1.0s") + self._thread = None + if self._fd is not None: + try: + os.close(self._fd) + finally: + self._fd = None + self._latest_sweep = None + self._reader_error = None + + @property + def published_count(self) -> int: + """Total number of sweeps the reader thread has produced.""" + with self._mailbox_cv: + return self._published_count + + def read_sweep( + self, + *, + timeout_s: float, + process: subprocess.Popen[bytes] | None = None, + ) -> RawSweep: + """Wait for and return the next published sweep. + + Raises :class:`TimeoutError` if none arrives within ``timeout_s``, + :class:`RuntimeError` if the collector process exited, and re-raises any + error caught by the reader thread. + """ + if self._thread is None: + raise RuntimeError("Kamil ADC TTY reader is not open") + deadline = time.monotonic() + float(timeout_s) + with self._mailbox_cv: + while True: + # Deliver a pending sweep first: if the reader both published a + # sweep and then died, the consumer still sees the good data and + # only meets the error on the next call. + if self._latest_sweep is not None: + sweep = self._latest_sweep + self._latest_sweep = None + return sweep + if self._reader_error is not None: + raise self._reader_error + raise_if_process_exited(process) + remaining_s = deadline - time.monotonic() + if remaining_s <= 0.0: + raise TimeoutError( + f"Timed out waiting for Kamil ADC sweep after {float(timeout_s):.3f}s" + ) + self._mailbox_cv.wait(timeout=min(_READ_POLL_INTERVAL_S, remaining_s)) + + # ------------------------------------------------------------------ + # Reader-thread internals + # ------------------------------------------------------------------ + + def _reader_loop(self) -> None: + """Drain the TTY, parse sweeps, and publish each completed one until stop.""" + parser = KamilAdcStreamParser() + try: + while not self._stop_event.is_set(): + chunk = self._read_available() + if not chunk: + continue + for sweep in parser.feed(chunk): + self._publish_sweep(sweep) + except Exception as exc: # noqa: BLE001 — surfaced to the consumer via read_sweep + logger.exception("Kamil ADC reader thread failed on %s", self.tty_path) + self._publish_error(exc) + + def _read_available(self) -> bytes: + """Block on ``select`` up to the poll interval; return new bytes (maybe empty). + + Returns ``b""`` when no data is ready yet or a stop was requested; raises + :class:`RuntimeError` on stream close or an unrecoverable read error. + """ + fd = self._fd + if fd is None or self._stop_event.is_set(): + return b"" + try: + readable, _, _ = select.select([fd], [], [], _READ_POLL_INTERVAL_S) + except InterruptedError: + return b"" + if not readable: + return b"" + try: + chunk = os.read(fd, _READ_CHUNK_BYTES) + except BlockingIOError: + return b"" + except OSError as exc: + if exc.errno in {errno.EAGAIN, errno.EWOULDBLOCK}: + return b"" + raise RuntimeError(f"Failed to read Kamil ADC TTY `{self.tty_path}`: {exc}") from exc + if not chunk: + raise RuntimeError(f"Kamil ADC TTY `{self.tty_path}` closed while reading") + return chunk + + def _publish_sweep(self, sweep: RawSweep) -> None: + """Store ``sweep`` as the latest mailbox value, overwriting any unread one.""" + with self._mailbox_cv: + self._latest_sweep = sweep + self._published_count += 1 + self._mailbox_cv.notify() + + def _publish_error(self, exc: Exception) -> None: + """Record ``exc`` as the reader fault and wake any waiter.""" + with self._mailbox_cv: + self._reader_error = exc + self._mailbox_cv.notify_all() diff --git a/python_app/hardware_full/kamil_adc_service.py b/python_app/hardware_full/kamil_adc_service.py deleted file mode 100644 index 3a58f9c..0000000 --- a/python_app/hardware_full/kamil_adc_service.py +++ /dev/null @@ -1,639 +0,0 @@ -"""Service for acquiring sweeps from the external Kamil ADC collector. - -The external `kamil_adc` binary publishes its samples on a PTY/TTY device as a -stream of 8-byte frames: - -* **Start marker**: `0x000A 0xFFFF 0xFFFF 0xFFFF` — delimits sweep boundaries. -* **Point frame**: `0x000A step real_i16 imag_i16` — one complex sample per - frame, with `step` running 1, 2, …, N for an N-point sweep. - -The hardware emits sweeps continuously, faster than callers tend to invoke -:meth:`KamilAdcService.acquire`. To avoid TTY-buffer overruns and stale data, -a daemon thread drains the device end of the TTY non-stop, parses complete -sweeps as they arrive, and publishes the **latest** one to a one-slot mailbox. -:meth:`acquire` simply waits for the next sweep to appear in that mailbox. - -Sweep length is determined by the first sweep observed at runtime and stays -constant for the life of the service; any later mismatch is treated as a -protocol violation rather than something to silently discard. -""" - -from __future__ import annotations - -from contextlib import suppress -from dataclasses import dataclass, field -import errno -import logging -import os -from pathlib import Path -import select -import signal -import stat -import struct -import subprocess -import threading -import time - -import numpy as np - -from python_app.hardware_full.librevna_driver.models import SweepResult -from python_app.models.run_config_model import RadarSweepModel, RunConfigModel - -logger = logging.getLogger(__name__) - -# Wire-format constants for the Kamil ADC TTY protocol. -KAMIL_ADC_MARKER = 0x000A -KAMIL_ADC_START_STEP = 0xFFFF -KAMIL_ADC_FRAME_BYTES = 8 - -_START_FRAME: bytes = struct.pack( - " complex: - """Parse one 8-byte point frame; validate marker and step ordering.""" - marker, step, real, imag = _POINT_STRUCT.unpack(frame) - if marker != KAMIL_ADC_MARKER: - raise ValueError(f"Kamil ADC marker mismatch: got 0x{marker:04x}, expected 0x000a") - if step != expected_step: - raise ValueError(f"Kamil ADC step mismatch: got {step}, expected {expected_step}") - return complex(real, imag) - - -@dataclass(slots=True) -class KamilAdcTtyReader: - """Background-thread TTY reader publishing the latest completed sweep. - - The reader spawns a daemon thread on :meth:`open` which continuously - drains the TTY, parses frames into complete sweeps, and stores the most - recent one in a single-slot mailbox. Consumers call :meth:`read_sweep` to - take that sweep; if a newer one arrives before the consumer reads, it - overwrites the previous unread value — by design, since consumers always - want the freshest data. - """ - - tty_path: str - _fd: int | None = field(init=False, default=None, repr=False) - _thread: threading.Thread | None = field(init=False, default=None, repr=False) - _stop_event: threading.Event = field(init=False, default_factory=threading.Event, repr=False) - _mailbox_cv: threading.Condition = field(init=False, default_factory=threading.Condition, repr=False) - _latest_sweep: np.ndarray | None = field(init=False, default=None, repr=False) - _reader_error: Exception | None = field(init=False, default=None, repr=False) - _locked_points: int | None = field(init=False, default=None, repr=False) - _published_count: int = field(init=False, default=0, repr=False) - - def open(self) -> None: - """Open the TTY and start the background reader thread.""" - if self._fd is not None: - return - self._fd = os.open(self.tty_path, os.O_RDONLY | os.O_NOCTTY | os.O_NONBLOCK) - self._stop_event.clear() - self._latest_sweep = None - self._reader_error = None - self._locked_points = None - self._published_count = 0 - self._thread = threading.Thread( - target=self._reader_loop, - name=f"kamil-adc-tty-reader[{self.tty_path}]", - daemon=True, - ) - self._thread.start() - logger.info("Kamil ADC TTY reader started on %s", self.tty_path) - - def close(self) -> None: - """Stop the reader thread and close the TTY descriptor.""" - logger.debug("Stopping Kamil ADC TTY reader on %s", self.tty_path) - self._stop_event.set() - with self._mailbox_cv: - self._mailbox_cv.notify_all() - if self._thread is not None: - self._thread.join(timeout=1.0) - if self._thread.is_alive(): - logger.warning("Kamil ADC reader thread did not stop within 1.0s") - self._thread = None - if self._fd is not None: - try: - os.close(self._fd) - finally: - self._fd = None - self._latest_sweep = None - self._reader_error = None - self._locked_points = None - - @property - def locked_points(self) -> int | None: - """Return the sweep point count established by the first sweep, or `None`.""" - return self._locked_points - - @property - def published_count(self) -> int: - """Return the total number of sweeps the reader thread has produced.""" - with self._mailbox_cv: - return self._published_count - - def read_sweep( - self, - *, - timeout_s: float, - process: subprocess.Popen[bytes] | None = None, - ) -> np.ndarray: - """Wait for and return the next published sweep. - - Raises :class:`TimeoutError` if no sweep arrives within `timeout_s`, - :class:`RuntimeError` if the external collector process exited, and - propagates any exception caught by the reader thread. - """ - if self._thread is None: - raise RuntimeError("Kamil ADC TTY reader is not open") - deadline = time.monotonic() + float(timeout_s) - with self._mailbox_cv: - while True: - # Always deliver a pending sweep first: if the reader thread - # both published a sweep and then died, the consumer should - # still see the good data and only meet the error on the next - # call. - if self._latest_sweep is not None: - sweep = self._latest_sweep - self._latest_sweep = None - return sweep - if self._reader_error is not None: - raise self._reader_error - self._raise_if_process_exited(process) - remaining_s = deadline - time.monotonic() - if remaining_s <= 0.0: - raise TimeoutError( - f"Timed out waiting for Kamil ADC sweep after {float(timeout_s):.3f}s" - ) - # Wake periodically so we can re-check process liveness. - self._mailbox_cv.wait(timeout=min(_READ_POLL_INTERVAL_S, remaining_s)) - - # ------------------------------------------------------------------ - # Reader-thread internals - # ------------------------------------------------------------------ - - def _reader_loop(self) -> None: - """Drain the TTY, parse frames, and publish completed sweeps until stop. - - Runs on the background reader thread. Any exception is logged and stored - so the next :meth:`read_sweep` re-raises it on the consumer thread. - """ - buffer = bytearray() - try: - if not self._skip_to_first_start_marker(buffer): - return - while not self._stop_event.is_set(): - sweep = self._read_one_sweep(buffer) - if sweep is None: - return - self._publish_sweep(sweep) - except Exception as exc: # noqa: BLE001 — surfaced to the consumer via read_sweep - logger.exception("Kamil ADC reader thread failed on %s", self.tty_path) - self._publish_error(exc) - - def _skip_to_first_start_marker(self, buffer: bytearray) -> bool: - """Discard pre-roll bytes until a start marker is consumed from `buffer`.""" - while not self._stop_event.is_set(): - start_index = buffer.find(_START_FRAME) - if start_index >= 0: - del buffer[: start_index + KAMIL_ADC_FRAME_BYTES] - return True - # Keep just enough trailing bytes that a marker split across read - # boundaries can still be reassembled on the next chunk. - if len(buffer) >= KAMIL_ADC_FRAME_BYTES: - del buffer[: -(KAMIL_ADC_FRAME_BYTES - 1)] - if not self._read_more(buffer): - return False - return False - - def _read_one_sweep(self, buffer: bytearray) -> np.ndarray | None: - """Parse frames from `buffer` until the next start marker; return the sweep.""" - values: list[complex] = [] - expected_step = 1 - while not self._stop_event.is_set(): - while len(buffer) < KAMIL_ADC_FRAME_BYTES: - if not self._read_more(buffer): - return None - frame = bytes(buffer[:KAMIL_ADC_FRAME_BYTES]) - del buffer[:KAMIL_ADC_FRAME_BYTES] - - if frame == _START_FRAME: - if not values: - # Two consecutive markers — ignore the empty sweep and keep parsing. - continue - self._validate_and_lock_point_count(len(values)) - return np.asarray(values, dtype=np.complex64) - - if self._locked_points is not None and expected_step > self._locked_points: - raise RuntimeError( - f"Kamil ADC sweep exceeded locked point count {self._locked_points} " - "without a start marker" - ) - values.append(_parse_point_frame(frame, expected_step)) - expected_step += 1 - return None - - def _validate_and_lock_point_count(self, points: int) -> None: - """Lock the point count on the first sweep; reject mismatches thereafter.""" - if self._locked_points is None: - self._locked_points = points - logger.info("Kamil ADC sweep point count locked to %d", points) - return - if points != self._locked_points: - raise RuntimeError( - f"Kamil ADC sweep length changed: locked={self._locked_points}, got={points}" - ) - - def _read_more(self, buffer: bytearray) -> bool: - """Block on `select` until bytes arrive, then append them to `buffer`. - - Returns `False` if the reader was asked to stop, `True` if at least one - byte was appended. Raises on stream-level errors. - """ - fd = self._fd - if fd is None: - return False - while not self._stop_event.is_set(): - try: - readable, _, _ = select.select([fd], [], [], _READ_POLL_INTERVAL_S) - except InterruptedError: - continue - if not readable: - continue - try: - chunk = os.read(fd, _READ_CHUNK_BYTES) - except BlockingIOError: - continue - except OSError as exc: - if exc.errno in {errno.EAGAIN, errno.EWOULDBLOCK}: - continue - raise RuntimeError( - f"Failed to read Kamil ADC TTY `{self.tty_path}`: {exc}" - ) from exc - if not chunk: - raise RuntimeError(f"Kamil ADC TTY `{self.tty_path}` closed while reading") - buffer.extend(chunk) - return True - return False - - def _publish_sweep(self, sweep: np.ndarray) -> None: - """Store `sweep` as the latest mailbox value, overwriting any prior unread one.""" - with self._mailbox_cv: - self._latest_sweep = sweep - self._published_count += 1 - self._mailbox_cv.notify() - - def _publish_error(self, exc: Exception) -> None: - """Record `exc` as the reader fault and wake any waiter.""" - with self._mailbox_cv: - self._reader_error = exc - self._mailbox_cv.notify_all() - - @staticmethod - def _raise_if_process_exited(process: subprocess.Popen[bytes] | None) -> None: - if process is None: - return - return_code = process.poll() - if return_code is not None: - raise RuntimeError(f"Kamil ADC process exited with code {return_code}") - - -@dataclass(slots=True) -class KamilAdcService: - """Launch the external `kamil_adc` collector and serve its sweeps.""" - - config: RunConfigModel - _process: subprocess.Popen[bytes] | None = field(init=False, default=None, repr=False) - _reader: KamilAdcTtyReader | None = field(init=False, default=None, repr=False) - _settings: RadarSweepModel | None = field(init=False, default=None, repr=False) - _frequency_hz: np.ndarray | None = field(init=False, default=None, repr=False) - - def __post_init__(self) -> None: - self._validate_config() - - @property - def command(self) -> list[str]: - """Return external collector command including the generated TTY argument.""" - adc = self.config.radar.kamil_adc - executable_path = str(Path(adc.executable_path).expanduser()) - return [executable_path, *adc.args, f"tty:{adc.tty_path}"] - - def open(self, *, stop_event: threading.Event | None = None) -> None: - """Launch the collector and start the TTY reader thread. - - An optional `stop_event` lets a caller abort the TTY-wait loop promptly - (e.g. on shutdown) instead of blocking for the full startup timeout. - """ - if self._reader is not None: - return - previous_tty_identity = _prepare_tty_path_for_collector(self.config.radar.kamil_adc.tty_path) - try: - self._start_process() - self._wait_for_tty(previous_tty_identity, stop_event=stop_event) - reader = KamilAdcTtyReader(self.config.radar.kamil_adc.tty_path) - reader.open() - self._reader = reader - logger.info("Kamil ADC service opened") - except Exception: - logger.exception("Kamil ADC service failed to open; cleaning up") - self.close() - raise - - def close(self) -> None: - """Stop the TTY reader and the external collector process.""" - logger.debug("Closing Kamil ADC service") - if self._reader is not None: - with suppress(Exception): - self._reader.close() - self._reader = None - self._stop_process() - - def configure(self, sweep: RadarSweepModel) -> None: - """Store sweep settings used to build the synthetic frequency axis.""" - self._validate_sweep(sweep) - self._settings = sweep - self._frequency_hz = None - logger.debug( - "Kamil ADC configured: frequency axis %s-%s Hz", sweep.start_hz, sweep.stop_hz - ) - - def read_device_limits(self) -> dict[str, float | int]: - """Kamil ADC has no runtime-readable sweep limit API.""" - raise RuntimeError("Kamil ADC device limits are not available") - - def acquire(self) -> SweepResult: - """Return the most recent completed sweep as S21 (S11 filled with zeros).""" - if self._settings is None: - raise RuntimeError("Kamil ADC service is not configured") - if self._reader is None: - raise RuntimeError("Kamil ADC service is not open") - process = self._process - if process is None or process.poll() is not None: - return_code = None if process is None else process.poll() - raise RuntimeError(f"Kamil ADC process is not running (code={return_code})") - - s21 = self._reader.read_sweep( - timeout_s=self.config.radar.kamil_adc.sweep_timeout_s, - process=process, - ) - points = int(s21.size) - if self._frequency_hz is None or self._frequency_hz.size != points: - logger.debug("Building Kamil ADC frequency axis for %d points", points) - self._frequency_hz = self._build_frequency_axis(points) - return SweepResult( - x=self._frequency_hz.copy(), - traces={ - "s11": np.zeros(points, dtype=np.complex64), - "s21": s21, - }, - ) - - # ------------------------------------------------------------------ - # Process / TTY lifecycle - # ------------------------------------------------------------------ - - def _start_process(self) -> None: - if self._process is not None and self._process.poll() is None: - return - adc = self.config.radar.kamil_adc - env = os.environ.copy() - env.update(adc.env) - logger.info("Starting Kamil ADC collector: %s", " ".join(self.command)) - self._process = subprocess.Popen( - self.command, - cwd=str(Path(adc.project_dir).expanduser()), - env=env, - stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, - stderr=subprocess.STDOUT, - start_new_session=True, - ) - - def _stop_process(self) -> None: - process = self._process - self._process = None - if process is None or process.poll() is not None: - return - logger.info("Stopping Kamil ADC collector (pid=%d)", process.pid) - with suppress(ProcessLookupError): - os.killpg(process.pid, signal.SIGTERM) - try: - process.wait(timeout=self.config.radar.kamil_adc.stop_timeout_s) - return - except subprocess.TimeoutExpired: - pass - logger.warning( - "Kamil ADC collector (pid=%d) ignored SIGTERM; sending SIGKILL", process.pid - ) - with suppress(ProcessLookupError): - os.killpg(process.pid, signal.SIGKILL) - process.wait(timeout=1.0) - - def _wait_for_tty( - self, - previous_identity: tuple[object, ...] | None, - *, - stop_event: threading.Event | None = None, - ) -> None: - adc = self.config.radar.kamil_adc - deadline = time.monotonic() + adc.startup_timeout_s - while time.monotonic() < deadline: - # Abort promptly if a stop was requested mid-wait. - if stop_event is not None and stop_event.is_set(): - raise RuntimeError("Kamil ADC startup aborted before TTY became available") - KamilAdcTtyReader._raise_if_process_exited(self._process) - identity = _tty_identity(adc.tty_path) - if identity is not None and identity != previous_identity: - return - # Use the stop event's wait() so a set() breaks the poll immediately. - if stop_event is not None: - if stop_event.wait(0.05): - raise RuntimeError("Kamil ADC startup aborted before TTY became available") - else: - time.sleep(0.05) - raise TimeoutError( - f"Timed out waiting for Kamil ADC TTY `{adc.tty_path}` to be created by the collector" - ) - - # ------------------------------------------------------------------ - # Validation helpers - # ------------------------------------------------------------------ - - def _validate_config(self) -> None: - if not self.config.is_kamil_adc: - raise RuntimeError("KamilAdcService requires radar.model='kamil_adc'") - if self.config.radar.driver_mode != "native": - raise RuntimeError("Kamil ADC requires radar.driver_mode='native'") - - adc = self.config.radar.kamil_adc - if not adc.project_dir: - raise ValueError("radar.kamil_adc.project_dir is required") - if not adc.executable_path: - raise ValueError("radar.kamil_adc.executable_path is required") - if not adc.tty_path: - raise ValueError("radar.kamil_adc.tty_path is required") - if any(arg.startswith("tty:") for arg in adc.args): - raise ValueError("radar.kamil_adc.args must not contain tty:; use tty_path instead") - if adc.startup_timeout_s <= 0.0: - raise ValueError("radar.kamil_adc.startup_timeout_s must be > 0") - if adc.sweep_timeout_s <= 0.0: - raise ValueError("radar.kamil_adc.sweep_timeout_s must be > 0") - if adc.stop_timeout_s <= 0.0: - raise ValueError("radar.kamil_adc.stop_timeout_s must be > 0") - - project_dir = Path(adc.project_dir).expanduser() - if not project_dir.is_dir(): - raise RuntimeError(f"radar.kamil_adc.project_dir is not a directory: {project_dir}") - executable_path = Path(adc.executable_path).expanduser() - if not executable_path.is_file(): - raise RuntimeError(f"radar.kamil_adc.executable_path is not a file: {executable_path}") - if not os.access(executable_path, os.X_OK): - raise RuntimeError(f"radar.kamil_adc.executable_path is not executable: {executable_path}") - - @staticmethod - def _validate_sweep(sweep: RadarSweepModel) -> None: - if float(sweep.stop_hz) < float(sweep.start_hz): - raise ValueError("Kamil ADC sweep stop_hz must be >= start_hz") - - def _build_frequency_axis(self, points: int) -> np.ndarray: - if self._settings is None: - raise RuntimeError("Kamil ADC service is not configured") - return np.linspace( - float(self._settings.start_hz), - float(self._settings.stop_hz), - int(points), - dtype=np.float32, - ) - - -def _tty_identity(path: str) -> tuple[object, ...] | None: - try: - if os.path.islink(path): - stat_result = os.lstat(path) - return ( - "link", - os.readlink(path), - int(stat_result.st_dev), - int(stat_result.st_ino), - int(stat_result.st_mtime_ns), - ) - stat_result = os.stat(path) - except FileNotFoundError: - return None - return ( - "node", - int(stat_result.st_dev), - int(stat_result.st_ino), - int(stat_result.st_mtime_ns), - ) - - -def _prepare_tty_path_for_collector(path: str) -> tuple[object, ...] | None: - """Remove stale generated TTY links before starting the external collector.""" - try: - stat_result = os.lstat(path) - except FileNotFoundError: - return None - - if stat.S_ISLNK(stat_result.st_mode) or stat.S_ISREG(stat_result.st_mode): - os.unlink(path) - return None - return None - - -def apply_kamil_adc_laser_control(config: RunConfigModel) -> bool: - """Apply the configured laser settings through the legacy device_main command sequence. - - Connects to the laser controller, resets it, and applies either manual or - variation mode per ``radar.laser_control``. Returns `True` when settings were - applied, `False` when laser control is disabled. The controller is always - disconnected before returning. - """ - laser = config.radar.laser_control - if not laser.enabled: - logger.debug("Kamil ADC laser control disabled; skipping") - return False - - _validate_laser_control_config(config) - - from python_app.hardware_full.laser_control.controller import DEVICE_MAIN_MESSAGE_ID, LaserController - from python_app.hardware_full.laser_control.models import VariationType - - controller = LaserController( - port=laser.port, - pi_coeff1_p=laser.pi_coeff1_p, - pi_coeff1_i=laser.pi_coeff1_i, - pi_coeff2_p=laser.pi_coeff2_p, - pi_coeff2_i=laser.pi_coeff2_i, - ) - try: - controller.connect() - controller.reset() - mode = laser.mode.strip().lower() - logger.info("Applying Kamil ADC laser control in %s mode", mode) - if mode == "manual": - manual = laser.manual - controller.set_manual_mode( - temp1=manual.temp1, - temp2=manual.temp2, - current1=manual.current1, - current2=manual.current2, - message_id=DEVICE_MAIN_MESSAGE_ID, - ) - return True - if mode == "variation": - variation = laser.variation - try: - variation_type = VariationType[variation.variation_type] - except KeyError as exc: - raise ValueError( - f"Unsupported radar.laser_control.variation.variation_type: {variation.variation_type}" - ) from exc - - controller.set_manual_mode( - temp1=variation.static_temp1, - temp2=variation.static_temp2, - current1=variation.static_current1, - current2=variation.static_current2, - message_id=DEVICE_MAIN_MESSAGE_ID, - ) - controller.start_variation( - variation_type=variation_type, - params={ - "static_temp1": variation.static_temp1, - "static_temp2": variation.static_temp2, - "static_current1": variation.static_current1, - "static_current2": variation.static_current2, - "min_value": variation.min_value, - "max_value": variation.max_value, - "step": variation.step, - "time_step": variation.time_step, - "delay_time": variation.delay_time, - }, - ) - return True - raise RuntimeError(f"Unsupported laser_control mode: {laser.mode}") - finally: - controller.disconnect() - - -def _validate_laser_control_config(config: RunConfigModel) -> None: - laser = config.radar.laser_control - if not laser.port: - raise ValueError("radar.laser_control.port is required when laser_control is enabled") - mode = laser.mode.strip().lower() - if mode not in {"manual", "variation"}: - raise ValueError("radar.laser_control.mode must be 'manual' or 'variation'") - if mode == "variation" and not laser.variation.variation_type: - raise ValueError("radar.laser_control.variation.variation_type is required") diff --git a/python_app/hardware_full/single_radar_service.py b/python_app/hardware_full/single_radar_service.py index 7ea0eca..dc6a5d0 100644 --- a/python_app/hardware_full/single_radar_service.py +++ b/python_app/hardware_full/single_radar_service.py @@ -5,7 +5,7 @@ from __future__ import annotations import logging from typing import Protocol -from python_app.hardware_full.kamil_adc_service import KamilAdcService +from python_app.hardware_full.kamil_adc import KamilAdcService from python_app.hardware_full.librevna_driver.models import SweepResult from python_app.hardware_full.librevna_service import LibreVnaService from python_app.hardware_full.remote_compact_m_k209_service import RemoteCompactMK209Service diff --git a/python_app/hardware_full/switch_service.py b/python_app/hardware_full/switch_service.py index 39c79a2..1a2abc2 100644 --- a/python_app/hardware_full/switch_service.py +++ b/python_app/hardware_full/switch_service.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass, field +from typing import TYPE_CHECKING from python_app.hardware_full.switch_drivers import ( H7992Driver, @@ -11,6 +12,9 @@ from python_app.hardware_full.switch_drivers import ( SwitchDriverProtocol, ) +if TYPE_CHECKING: + from python_app.models.run_config_schema import SwitchModel + @dataclass(slots=True) class SwitchService: @@ -31,6 +35,25 @@ class SwitchService: """Create underlying driver based on configured mode and type.""" self._driver = self._build_driver() + @classmethod + def from_model(cls, model: SwitchModel) -> SwitchService: + """Build a switch service from a ``SwitchModel`` config section. + + The single construction path shared by every acquisition producer and + capture workflow, so all devices drive switches identically. + """ + return cls( + name=model.name, + positions=model.positions, + mode=model.driver_mode, + driver=model.driver, + gpio_chip=model.gpio_chip, + pin_a=model.pin_a, + pin_b=model.pin_b, + invert_logic=model.invert_logic, + default_position=model.default_position, + ) + def open(self) -> None: """Open underlying switch driver resources.""" self._driver.open() diff --git a/python_app/models/gui_profile_codec.py b/python_app/models/gui_profile_codec.py index ded16d1..84c20ff 100644 --- a/python_app/models/gui_profile_codec.py +++ b/python_app/models/gui_profile_codec.py @@ -185,6 +185,12 @@ def gui_profile_from_dict(payload: dict[str, Any]) -> GuiProfileModel: gui.processing.pass_through.show_phase, "gui.processing.pass_through", ), + unwrap_phase=_optional_bool( + pass_through_object, + "unwrap_phase", + gui.processing.pass_through.unwrap_phase, + "gui.processing.pass_through", + ), combo_filter=_optional_string( pass_through_object, "combo_filter", @@ -521,6 +527,7 @@ def gui_profile_to_dict(model: GuiProfileModel) -> dict[str, Any]: "pass_through": { "show_magnitude": gui.processing.pass_through.show_magnitude, "show_phase": gui.processing.pass_through.show_phase, + "unwrap_phase": gui.processing.pass_through.unwrap_phase, "combo_filter": gui.processing.pass_through.combo_filter, "fixed_y_enabled": gui.processing.pass_through.fixed_y_enabled, "y_min_db": gui.processing.pass_through.y_min_db, diff --git a/python_app/models/gui_profile_schema.py b/python_app/models/gui_profile_schema.py index af7c17b..91afb85 100644 --- a/python_app/models/gui_profile_schema.py +++ b/python_app/models/gui_profile_schema.py @@ -30,6 +30,7 @@ class GuiPassThroughStateModel: show_magnitude: bool = True show_phase: bool = True + unwrap_phase: bool = False combo_filter: str = "" fixed_y_enabled: bool = False y_min_db: float = -100.0 diff --git a/python_app/models/run_config_codec.py b/python_app/models/run_config_codec.py index 196be07..d9b8cea 100644 --- a/python_app/models/run_config_codec.py +++ b/python_app/models/run_config_codec.py @@ -239,6 +239,19 @@ def run_config_from_dict(payload: dict[str, Any]) -> RunConfigModel: model.radar.kamil_adc.stop_timeout_s = _read_float( kamil_adc_payload, "stop_timeout_s", model.radar.kamil_adc.stop_timeout_s ) + phase_calibration_payload = _as_dict( + kamil_adc_payload.get("phase_calibration"), "radar.kamil_adc.phase_calibration" + ) + calibration = model.radar.kamil_adc.phase_calibration + calibration.phase0_rad = _read_float(phase_calibration_payload, "phase0_rad", calibration.phase0_rad) + calibration.freq0_hz = _read_float(phase_calibration_payload, "freq0_hz", calibration.freq0_hz) + calibration.phase1_rad = _read_float(phase_calibration_payload, "phase1_rad", calibration.phase1_rad) + calibration.freq1_hz = _read_float(phase_calibration_payload, "freq1_hz", calibration.freq1_hz) + band_payload = _as_dict(kamil_adc_payload.get("band"), "radar.kamil_adc.band") + band = model.radar.kamil_adc.band + band.start_hz = _read_float(band_payload, "start_hz", band.start_hz) + band.stop_hz = _read_float(band_payload, "stop_hz", band.stop_hz) + band.points = _read_int(band_payload, "points", band.points) model.radar.laser_control.enabled = _read_bool( laser_control_payload, "enabled", model.radar.laser_control.enabled @@ -490,6 +503,17 @@ def run_config_to_dict(model: RunConfigModel) -> dict[str, Any]: "startup_timeout_s": model.radar.kamil_adc.startup_timeout_s, "sweep_timeout_s": model.radar.kamil_adc.sweep_timeout_s, "stop_timeout_s": model.radar.kamil_adc.stop_timeout_s, + "phase_calibration": { + "phase0_rad": model.radar.kamil_adc.phase_calibration.phase0_rad, + "freq0_hz": model.radar.kamil_adc.phase_calibration.freq0_hz, + "phase1_rad": model.radar.kamil_adc.phase_calibration.phase1_rad, + "freq1_hz": model.radar.kamil_adc.phase_calibration.freq1_hz, + }, + "band": { + "start_hz": model.radar.kamil_adc.band.start_hz, + "stop_hz": model.radar.kamil_adc.band.stop_hz, + "points": model.radar.kamil_adc.band.points, + }, }, "laser_control": { "enabled": model.radar.laser_control.enabled, diff --git a/python_app/models/run_config_model.py b/python_app/models/run_config_model.py index cd453bc..3b4791a 100644 --- a/python_app/models/run_config_model.py +++ b/python_app/models/run_config_model.py @@ -6,7 +6,9 @@ from python_app.models.run_config_schema import ( GprModel, GprRxGeometryModel, GprTxGeometryModel, + KamilAdcBandModel, KamilAdcModel, + KamilAdcPhaseCalibrationModel, LaserControlModel, LaserManualModeModel, LaserVariationModeModel, @@ -37,7 +39,9 @@ __all__ = [ "GprModel", "GprRxGeometryModel", "GprTxGeometryModel", + "KamilAdcBandModel", "KamilAdcModel", + "KamilAdcPhaseCalibrationModel", "LaserControlModel", "LaserManualModeModel", "LaserVariationModeModel", diff --git a/python_app/models/run_config_schema.py b/python_app/models/run_config_schema.py index 5441f89..2250d73 100644 --- a/python_app/models/run_config_schema.py +++ b/python_app/models/run_config_schema.py @@ -42,6 +42,38 @@ class RadarMultiDeviceModel: recovery_attempts: int = 3 +@dataclass(slots=True) +class KamilAdcPhaseCalibrationModel: + """Affine law mapping the reference signal's unwrapped phase to frequency. + + Two fixed anchor points ``(phase0_rad, freq0_hz)`` and ``(phase1_rad, freq1_hz)`` + define ``f(phase) = freq0_hz + (phase - phase0_rad) * (freq1_hz - freq0_hz) + / (phase1_rad - phase0_rad)``, applied to the *absolute* unwrapped phase of + every sweep. These are physical constants of the reference arm and must be + supplied by config — never derived from a live sweep. + """ + + phase0_rad: float = 0.0 + freq0_hz: float = 2_046_000_000.0 + phase1_rad: float = 300.0 + freq1_hz: float = 5_612_000_000.0 + + +@dataclass(slots=True) +class KamilAdcBandModel: + """Fixed frequency window every sweep is cropped to and resampled onto. + + Each sweep is resampled onto ``linspace(start_hz, stop_hz, points)`` so all + sweeps share one identical axis and can be averaged/subtracted. The window + must lie inside the (floating) range each sweep actually covers; sweeps that + fail to cover it are rejected rather than edge-extrapolated. + """ + + start_hz: float = 2_100_000_000.0 + stop_hz: float = 5_500_000_000.0 + points: int = 2048 + + @dataclass(slots=True) class KamilAdcModel: """External Kamil ADC acquisition process settings.""" @@ -54,6 +86,10 @@ class KamilAdcModel: startup_timeout_s: float = 5.0 sweep_timeout_s: float = 5.0 stop_timeout_s: float = 2.0 + phase_calibration: KamilAdcPhaseCalibrationModel = field( + default_factory=KamilAdcPhaseCalibrationModel + ) + band: KamilAdcBandModel = field(default_factory=KamilAdcBandModel) @dataclass(slots=True) diff --git a/python_app/orchestration/process_supervisor.py b/python_app/orchestration/process_supervisor.py index 224d022..39c2f4e 100644 --- a/python_app/orchestration/process_supervisor.py +++ b/python_app/orchestration/process_supervisor.py @@ -23,6 +23,10 @@ logger = logging.getLogger(__name__) _LOG_MAX_BYTES = 8 * 1024 * 1024 # Per-process force-kill deadline used on stop; each child gets its own window. _STOP_GRACE_SECONDS = 2.0 +# Cmdline marker for the Kamil ADC collector binary. The collector runs in its own +# session, so it can outlive a force-killed acquisition producer while still holding +# the L-Card E-502; we hunt it by name as a guaranteed device-release backstop. +_KAMIL_COLLECTOR_MARKER = b"kamil_adc_collector" @dataclass(slots=True) @@ -135,6 +139,9 @@ class ProcessSupervisor: """Start required pipeline binaries and wait until they are ready.""" if self.is_running(): raise RuntimeError("Acquisition processes are already running") + # Clean slate: an ADC collector orphaned by a prior crash/force-kill can + # still hold the E-502 and make a fresh acquisition fail to open. + self._kill_kamil_collectors() logger.info( "Starting pipeline from config %s (allow_clean_orchestrator_exit=%s)", @@ -186,6 +193,7 @@ class ProcessSupervisor: def stop(self) -> None: """Stop acquisition-side processes, keep processor process intact.""" self._stop_processes(["sweep_orchestrator", "data_preprocessor"]) + self._kill_kamil_collectors() def stop_orchestrator(self) -> None: """Stop orchestrator process only.""" @@ -198,6 +206,7 @@ class ProcessSupervisor: def stop_all(self) -> None: """Stop all managed processes.""" self._stop_processes(["sweep_orchestrator", "data_preprocessor", "data_processor"]) + self._kill_kamil_collectors() def _spawn(self, name: str, command: list[str], *, allow_clean_exit: bool) -> None: """Spawn one process unless same process is already alive. @@ -353,6 +362,32 @@ class ProcessSupervisor: except (ProcessLookupError, PermissionError): pass + def _kill_kamil_collectors(self) -> None: + """SIGKILL any Kamil ADC collector found by name — a guaranteed device backstop. + + The collector survives a force-killed producer (own session) and can hang + the E-502, so it is hunted by cmdline and its group killed hard, regardless + of who its parent is. Best-effort and never raises. + """ + own_pid = os.getpid() + try: + pids = [int(entry.name) for entry in Path("/proc").iterdir() if entry.name.isdigit()] + except OSError: + return + killed = 0 + for pid in pids: + if pid <= 1 or pid == own_pid: + continue + try: + cmdline = (Path("/proc") / str(pid) / "cmdline").read_bytes() + except OSError: + continue + if _KAMIL_COLLECTOR_MARKER in cmdline: + self._signal_group(pid, signal.SIGKILL) + killed += 1 + if killed: + logger.warning("Force-killed %d orphaned Kamil ADC collector process(es)", killed) + @staticmethod def _log_abnormal_stop_exit(process: ManagedProcess) -> None: """Log a process that self-exited abnormally around stop time. @@ -523,6 +558,9 @@ class ProcessSupervisor: self._pidfile_path.write_text("", encoding="utf-8") except OSError: pass + # The ADC collector is a grandchild (spawned by the producer), not in the + # pidfile, so reap it separately by name. + self._kill_kamil_collectors() def _is_stale_pipeline_pid(self, pid: int) -> bool: """Return whether `pid` still runs one of our pipeline binaries/scripts. diff --git a/python_app/scripts/kamil_adc_calibrate.py b/python_app/scripts/kamil_adc_calibrate.py new file mode 100644 index 0000000..0e799ea --- /dev/null +++ b/python_app/scripts/kamil_adc_calibrate.py @@ -0,0 +1,265 @@ +"""Calibrate the Kamil ADC reference-phase frequency law from live sweeps. + +Captures many raw sweeps from the collector and reports — and optionally writes +back — the calibration the runtime uses: + +* ``phase0_rad`` / ``phase1_rad`` — the unwrapped reference phase at the sweep + start and stop, taken as the MEDIAN over all captured sweeps. ``freq0_hz`` / + ``freq1_hz`` (the known sweep endpoints) come from config and are kept as-is. +* ``band.points`` — recommended as the median number of usable points landing + inside ``[band.start_hz, band.stop_hz]``, so the fixed output grid matches the + native density rather than inflating it. + +It also reports how reliably the configured band is covered (sweeps that do not +span it are rejected at runtime) and how many points the crop discards. + +The collector is launched with the ``do8_freq_ref`` arguments regardless of what +the on-disk config says, so the reference channel is always present. With +``--apply`` the config is migrated to that collector/args and the calibrated +anchors + recommended point count are written back. + +Run on the Pi, e.g.:: + + .venv/bin/python -m python_app.scripts.kamil_adc_calibrate \ + --config run_config_kamil_adc.pi.json --sweeps 200 --apply +""" + +from __future__ import annotations + +import argparse +from contextlib import suppress +import json +import logging +from pathlib import Path +import statistics +import time + +import numpy as np + +from python_app.hardware_full.kamil_adc import KamilAdcService, apply_kamil_adc_laser_control +from python_app.models.run_config_model import RunConfigModel + +logger = logging.getLogger("kamil_adc_calibrate") + +# The collector arguments that enable the DI8 reference overlay (mirrors the +# kamil example config / run_do8_freq_ref.sh). Forced on so calibration always +# sees the reference channel even if the on-disk config predates it. +DO8_FREQ_REF_ARGS = [ + "profile:phase", + "clock:internal", + "internal_ref_hz:2000000", + "start:di_syn2_rise", + "stop:di_syn2_fall", + "sample_clock_hz:max", + "range:2", + "duration_ms:100", + "packet_limit:0", + "do1_toggle_per_frame", + "do1_pair_subtract_avg", + "do8_freq_ref", + "do8_cycle_period:8", +] +COLLECTOR_PATH = "build/bin/kamil_adc_collector" + + +def _open_with_retry(service: KamilAdcService, *, attempts: int = 4, delay_s: float = 8.0) -> None: + """Open the collector, retrying the transient E-502 device-busy after a close. + + The L-Card device is not always reacquirable immediately after a previous + collector released it; a short backoff lets it settle before the next try. + """ + for attempt in range(1, attempts + 1): + try: + service.open() + return + except Exception as exc: # noqa: BLE001 — device-busy is expected and retried + logger.warning("Collector open failed (attempt %d/%d): %s", attempt, attempts, exc) + with suppress(Exception): + service.close() + if attempt < attempts: + time.sleep(delay_s) + raise RuntimeError(f"Collector did not open after {attempts} attempts") + + +def _capture_reference_phases(service: KamilAdcService, *, warmup: int, sweeps: int) -> list[np.ndarray]: + """Capture `sweeps` reference-phase arrays after discarding `warmup` sweeps.""" + for index in range(warmup): + service.read_raw_sweep() + if index == 0: + logger.info("Warming up (%d sweeps) while the sweep settles...", warmup) + + phases: list[np.ndarray] = [] + for index in range(sweeps): + raw = service.read_raw_sweep() + if raw.reference.size >= 2: + phases.append(np.unwrap(np.angle(raw.reference.astype(np.complex128)))) + if (index + 1) % 50 == 0: + logger.info("Captured %d/%d sweeps", index + 1, sweeps) + return phases + + +def _summarize(values: np.ndarray) -> str: + """Compact min / median / max summary for a 1-D array.""" + return f"min={np.min(values):.6g} median={np.median(values):.6g} max={np.max(values):.6g}" + + +def _analyze(phases: list[np.ndarray], config: RunConfigModel) -> dict: + """Derive the calibration and band diagnostics from captured phase arrays.""" + kamil = config.radar.kamil_adc + freq0, freq1 = kamil.phase_calibration.freq0_hz, kamil.phase_calibration.freq1_hz + band_start, band_stop = kamil.band.start_hz, kamil.band.stop_hz + + phase0 = float(statistics.median(float(phase[0]) for phase in phases)) + phase1 = float(statistics.median(float(phase[-1]) for phase in phases)) + if phase1 == phase0: + raise RuntimeError("Degenerate calibration: median start and stop phases are equal") + slope = (freq1 - freq0) / (phase1 - phase0) + + total_points = np.array([phase.size for phase in phases], dtype=np.float64) + f_starts, f_stops, in_band_counts, covers = [], [], [], [] + for phase in phases: + freqs = freq0 + (phase - phase0) * slope + lo, hi = float(np.min(freqs)), float(np.max(freqs)) + f_starts.append(lo) + f_stops.append(hi) + in_band_counts.append(int(np.count_nonzero((freqs >= band_start) & (freqs <= band_stop)))) + covers.append(lo <= band_start and hi >= band_stop) + + in_band = np.array(in_band_counts, dtype=np.float64) + f_start = np.array(f_starts) + f_stop = np.array(f_stops) + recommended_points = int(round(float(np.median(in_band)))) + # A band that ~95% of sweeps satisfy on each edge: start at the 95th percentile + # of per-sweep start frequencies, stop at the 5th percentile of stop frequencies. + rec_band_start = float(np.quantile(f_start, 0.95)) + rec_band_stop = float(np.quantile(f_stop, 0.05)) + rec_coverage = float(np.mean((f_start <= rec_band_start) & (f_stop >= rec_band_stop))) + return { + "sweeps": len(phases), + "phase0_rad": phase0, + "phase1_rad": phase1, + "phase_span_rad": phase1 - phase0, + "phase0_mad_rad": float(np.median(np.abs([float(p[0]) - phase0 for p in phases]))), + "phase1_mad_rad": float(np.median(np.abs([float(p[-1]) - phase1 for p in phases]))), + "freq0_hz": freq0, + "freq1_hz": freq1, + "band_start_hz": band_start, + "band_stop_hz": band_stop, + "total_points_median": float(np.median(total_points)), + "in_band_points_median": float(np.median(in_band)), + "recommended_points": recommended_points, + "cropped_fraction": 1.0 - float(np.median(in_band)) / float(np.median(total_points)), + "coverage_fraction": float(np.mean(covers)), + "lower_ok_fraction": float(np.mean(f_start <= band_start)), + "upper_ok_fraction": float(np.mean(f_stop >= band_stop)), + "rec_band_start_hz": rec_band_start, + "rec_band_stop_hz": rec_band_stop, + "rec_coverage_fraction": rec_coverage, + "f_start": f_start, + "f_stop": f_stop, + } + + +def _report(result: dict) -> None: + """Print a human-readable calibration report.""" + print("\n" + "=" * 72) + print(f"Kamil ADC calibration over {result['sweeps']} sweeps") + print("=" * 72) + print( + f"phase0_rad = {result['phase0_rad']:.6f} (median start phase, MAD " + f"{result['phase0_mad_rad']:.4f}) -> {result['freq0_hz'] / 1e9:.4f} GHz" + ) + print( + f"phase1_rad = {result['phase1_rad']:.6f} (median stop phase, MAD " + f"{result['phase1_mad_rad']:.4f}) -> {result['freq1_hz'] / 1e9:.4f} GHz" + ) + print(f"phase span = {result['phase_span_rad']:.4f} rad") + print( + f"points/sweep: total median={result['total_points_median']:.0f}, " + f"in-band median={result['in_band_points_median']:.0f}" + ) + print(f"recommended band.points = {result['recommended_points']}") + print( + f"band [{result['band_start_hz'] / 1e9:.3f}, {result['band_stop_hz'] / 1e9:.3f}] GHz: " + f"covered by {result['coverage_fraction'] * 100:.1f}% of sweeps " + f"(start<=lo: {result['lower_ok_fraction'] * 100:.1f}%, stop>=hi: {result['upper_ok_fraction'] * 100:.1f}%), " + f"{result['cropped_fraction'] * 100:.1f}% of points cropped" + ) + print(f"per-sweep start freq (GHz): {_summarize(result['f_start'] / 1e9)}") + print(f"per-sweep stop freq (GHz): {_summarize(result['f_stop'] / 1e9)}") + print( + f"suggested band for ~95%/edge: [{result['rec_band_start_hz'] / 1e9:.3f}, " + f"{result['rec_band_stop_hz'] / 1e9:.3f}] GHz -> covers " + f"{result['rec_coverage_fraction'] * 100:.1f}% of sweeps" + ) + if result["coverage_fraction"] < 0.95: + print( + "WARNING: many sweeps do not cover the configured band and would be rejected; " + "consider the suggested band above (or longer laser settling if sweeps are partial)." + ) + print("=" * 72 + "\n") + + +def _apply(config_path: Path, result: dict) -> None: + """Write the migrated collector args + calibrated anchors + points to the config.""" + payload = json.loads(config_path.read_text(encoding="utf-8")) + kamil = payload.setdefault("radar", {}).setdefault("kamil_adc", {}) + kamil["executable_path"] = COLLECTOR_PATH + kamil["args"] = list(DO8_FREQ_REF_ARGS) + kamil["phase_calibration"] = { + "phase0_rad": result["phase0_rad"], + "freq0_hz": result["freq0_hz"], + "phase1_rad": result["phase1_rad"], + "freq1_hz": result["freq1_hz"], + } + kamil["band"] = { + "start_hz": result["band_start_hz"], + "stop_hz": result["band_stop_hz"], + "points": result["recommended_points"], + } + config_path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8") + print(f"Applied calibration to {config_path}") + + +def main() -> int: + parser = argparse.ArgumentParser(description="Calibrate Kamil ADC reference phase -> frequency") + parser.add_argument("--config", required=True, type=Path, help="Path to the kamil_adc run config") + parser.add_argument("--sweeps", type=int, default=200, help="Sweeps to average (default 200)") + parser.add_argument("--warmup", type=int, default=10, help="Sweeps to discard first (default 10)") + parser.add_argument("--apply", action="store_true", help="Write the calibration back to --config") + parser.add_argument("--no-laser", action="store_true", help="Skip laser setup (already running)") + args = parser.parse_args() + + logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s") + config = RunConfigModel.load_from_path(args.config) + if not config.is_kamil_adc: + raise SystemExit("Config is not a kamil_adc profile") + + # Force the reference-producing collector regardless of the on-disk config. + config.radar.kamil_adc.executable_path = COLLECTOR_PATH + config.radar.kamil_adc.args = list(DO8_FREQ_REF_ARGS) + + if not args.no_laser: + logger.info("Applying laser control...") + apply_kamil_adc_laser_control(config) + + service = KamilAdcService(config) + logger.info("Opening collector: %s", " ".join(service.command)) + _open_with_retry(service) + try: + phases = _capture_reference_phases(service, warmup=args.warmup, sweeps=args.sweeps) + finally: + service.close() + + if len(phases) < max(2, args.sweeps // 2): + raise SystemExit(f"Only {len(phases)} usable sweeps captured; check the reference signal") + + result = _analyze(phases, config) + _report(result) + if args.apply: + _apply(args.config, result) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/python_app/scripts/kamil_adc_raw_producer.py b/python_app/scripts/kamil_adc_raw_producer.py index fc22178..318dfc0 100644 --- a/python_app/scripts/kamil_adc_raw_producer.py +++ b/python_app/scripts/kamil_adc_raw_producer.py @@ -12,10 +12,10 @@ import time import numpy as np -from python_app.hardware_full.kamil_adc_service import KamilAdcService +from python_app.hardware_full.kamil_adc import KamilAdcService from python_app.hardware_full.switch_service import SwitchService from python_app.models.dataset_model import ComboKey, SweepCollection, TraceData -from python_app.models.run_config_model import RunConfigModel, SwitchModel +from python_app.models.run_config_model import RunConfigModel from python_app.orchestration.shm import ShmRingWriter from python_app.storage.npz.serialize import RAW_MAGIC, serialize_trace_collection @@ -137,8 +137,8 @@ def main() -> int: config.rings.raw.name, config.rings.raw_tap.name, ) radar = KamilAdcService(config) - input_switch = _switch_from_model(config.input_switch) - output_switch = _switch_from_model(config.output_switch) + input_switch = SwitchService.from_model(config.input_switch) + output_switch = SwitchService.from_model(config.output_switch) try: if not _open_radar_with_retry(config, radar, input_switch, output_switch, stop_requested): @@ -226,7 +226,8 @@ def main() -> int: output_switch.close() with suppress(Exception): input_switch.close() - radar.close() + with suppress(Exception): + radar.close() raw_tap_writer.close() raw_writer.close() @@ -234,19 +235,5 @@ def main() -> int: return 0 -def _switch_from_model(model: SwitchModel) -> SwitchService: - return SwitchService( - name=model.name, - positions=model.positions, - mode=model.driver_mode, - driver=model.driver, - gpio_chip=model.gpio_chip, - pin_a=model.pin_a, - pin_b=model.pin_b, - invert_logic=model.invert_logic, - default_position=model.default_position, - ) - - if __name__ == "__main__": raise SystemExit(main()) diff --git a/python_app/tests/test_gui_profile_codec.py b/python_app/tests/test_gui_profile_codec.py index fa574ab..fdd5099 100644 --- a/python_app/tests/test_gui_profile_codec.py +++ b/python_app/tests/test_gui_profile_codec.py @@ -30,6 +30,22 @@ class GuiProfileCodecTest(unittest.TestCase): self.assertEqual(decoded.gui.processing.pass_through.combo_filter, "0:0,1:0") self.assertEqual(encoded["gui"]["processing"]["pass_through"]["combo_filter"], "0:0,1:0") + def test_pass_through_unwrap_phase_round_trips(self) -> None: + profile = GuiProfileModel( + gui=GuiStateModel( + processing=GuiProcessingStateModel( + pass_through=GuiPassThroughStateModel(unwrap_phase=True) + ) + ) + ) + + encoded = profile.to_dict() + decoded = GuiProfileModel.from_dict(encoded) + + assert decoded.gui is not None + self.assertTrue(decoded.gui.processing.pass_through.unwrap_phase) + self.assertTrue(encoded["gui"]["processing"]["pass_through"]["unwrap_phase"]) + def test_default_profile_round_trips_idempotently(self) -> None: # Full-subtree idempotence catches field-drop/mis-map regressions across every # sub-model, which the single combo_filter round-trip above cannot. diff --git a/python_app/tests/test_kamil_adc_neutral_preprocess.py b/python_app/tests/test_kamil_adc_neutral_preprocess.py index 2b5d4df..e0f9dbd 100644 --- a/python_app/tests/test_kamil_adc_neutral_preprocess.py +++ b/python_app/tests/test_kamil_adc_neutral_preprocess.py @@ -10,35 +10,26 @@ from python_app.models.run_config_model import RunConfigModel from python_app.workflows.kamil_adc_neutral_preprocess import build_kamil_adc_neutral_s21_sets +def _kamil_config(*, points: int = 5) -> RunConfigModel: + return RunConfigModel.from_dict( + { + "radar": { + "model": "kamil_adc", + "kamil_adc": { + "band": {"start_hz": 2_100_000_000.0, "stop_hz": 5_500_000_000.0, "points": points}, + }, + }, + "switches": {"port1": {"positions": 1}, "port2": {"positions": 2}}, + "run": {"combos": [{"input": 0, "output": 0}, {"input": 1, "output": 0}]}, + } + ) + + class KamilAdcNeutralPreprocessTest(unittest.TestCase): - def test_builds_passthrough_s21_sets_for_current_sweep(self) -> None: - config = RunConfigModel.from_dict( - { - "radar": { - "model": "kamil_adc", - "sweep": { - "start_hz": 1_000_000.0, - "stop_hz": 4_000_000.0, - "if_bandwidth_hz": 1.0, - "stimulus_power_dbm": -10.0, - }, - }, - "switches": { - "port1": {"positions": 1}, - "port2": {"positions": 2}, - }, - "run": { - "combos": [ - {"input": 0, "output": 0}, - {"input": 1, "output": 0}, - ], - }, - } - ) + def test_builds_passthrough_s21_sets_on_band_grid(self) -> None: + calibration, reference = build_kamil_adc_neutral_s21_sets(_kamil_config(points=5)) - calibration, reference = build_kamil_adc_neutral_s21_sets(config, point_count=4) - - expected_frequency = np.linspace(1_000_000.0, 4_000_000.0, 4, dtype=np.float32) + expected_frequency = np.linspace(2_100_000_000.0, 5_500_000_000.0, 5).astype(np.float32) self.assertEqual(len(calibration.traces), 2) self.assertEqual(len(reference.traces), 2) self.assertEqual( @@ -47,47 +38,35 @@ class KamilAdcNeutralPreprocessTest(unittest.TestCase): ) for trace in calibration.traces: np.testing.assert_array_equal(trace.frequency_hz, expected_frequency) - np.testing.assert_array_equal(trace.s11, np.zeros(4, dtype=np.complex64)) - np.testing.assert_array_equal(trace.s21, np.ones(4, dtype=np.complex64)) + np.testing.assert_array_equal(trace.s11, np.zeros(5, dtype=np.complex64)) + np.testing.assert_array_equal(trace.s21, np.ones(5, dtype=np.complex64)) for trace in reference.traces: np.testing.assert_array_equal(trace.frequency_hz, expected_frequency) - np.testing.assert_array_equal(trace.s11, np.zeros(4, dtype=np.complex64)) - np.testing.assert_array_equal(trace.s21, np.zeros(4, dtype=np.complex64)) + np.testing.assert_array_equal(trace.s21, np.zeros(5, dtype=np.complex64)) + + def test_grid_matches_processor_grid(self) -> None: + """The neutral axis must be byte-identical to the acquisition grid.""" + from python_app.hardware_full.kamil_adc import ( + KamilAdcProcessingParams, + KamilAdcSweepProcessor, + ) + + config = _kamil_config(points=17) + processor = KamilAdcSweepProcessor(KamilAdcProcessingParams.from_kamil_model(config.radar.kamil_adc)) + calibration, _reference = build_kamil_adc_neutral_s21_sets(config) + np.testing.assert_array_equal(calibration.traces[0].frequency_hz, processor.grid_hz) def test_rejects_non_kamil_config(self) -> None: config = RunConfigModel.from_dict({"radar": {"model": "librevna"}}) - with self.assertRaisesRegex(ValueError, "kamil_adc"): - build_kamil_adc_neutral_s21_sets(config, point_count=4) + build_kamil_adc_neutral_s21_sets(config) - @staticmethod - def _kamil_config() -> RunConfigModel: - return RunConfigModel.from_dict( - { - "radar": { - "model": "kamil_adc", - "sweep": {"start_hz": 1_000_000.0, "stop_hz": 4_000_000.0, - "if_bandwidth_hz": 1.0, "stimulus_power_dbm": -10.0}, - }, - "switches": {"port1": {"positions": 1}, "port2": {"positions": 2}}, - "run": {"combos": [{"input": 0, "output": 0}, {"input": 1, "output": 0}]}, - } - ) - - def test_point_count_zero_or_negative_raises(self) -> None: - config = self._kamil_config() - for bad in (0, -1): - with self.subTest(point_count=bad), self.assertRaisesRegex(ValueError, "point count"): - build_kamil_adc_neutral_s21_sets(config, point_count=bad) - - def test_single_point_sweep(self) -> None: - calibration, _reference = build_kamil_adc_neutral_s21_sets(self._kamil_config(), point_count=1) - for trace in calibration.traces: - self.assertEqual(trace.frequency_hz.tolist(), [1_000_000.0]) - self.assertEqual(trace.s21.shape, (1,)) + def test_rejects_degenerate_band(self) -> None: + with self.assertRaisesRegex(ValueError, "points must be >= 2"): + build_kamil_adc_neutral_s21_sets(_kamil_config(points=1)) def test_dtypes_are_float32_and_complex64(self) -> None: - calibration, _reference = build_kamil_adc_neutral_s21_sets(self._kamil_config(), point_count=4) + calibration, _reference = build_kamil_adc_neutral_s21_sets(_kamil_config()) trace = calibration.traces[0] self.assertEqual(trace.frequency_hz.dtype, np.float32) self.assertEqual(trace.s21.dtype, np.complex64) @@ -96,7 +75,7 @@ class KamilAdcNeutralPreprocessTest(unittest.TestCase): def test_calibration_s21_is_a_nonzero_divisor(self) -> None: # The C++ through-calibrator divides measured/calibration, so calibration S21 # must never be zero — that is the whole point of the '1+0j neutral' contract. - calibration, _reference = build_kamil_adc_neutral_s21_sets(self._kamil_config(), point_count=4) + calibration, _reference = build_kamil_adc_neutral_s21_sets(_kamil_config()) for trace in calibration.traces: self.assertTrue(bool(np.all(trace.s21 != 0))) diff --git a/python_app/tests/test_kamil_adc_processing.py b/python_app/tests/test_kamil_adc_processing.py new file mode 100644 index 0000000..288885a --- /dev/null +++ b/python_app/tests/test_kamil_adc_processing.py @@ -0,0 +1,172 @@ +"""Tests for the Kamil ADC reference-channel signal processing. + +References are built from an explicit *unwrapped* phase ramp whose first sample +lies in ``(-pi, pi]`` and whose steps are below ``pi``, so that +``np.unwrap(np.angle(ref))`` recovers exactly the phase we specify. This mirrors +how a real swept reference behaves (absolute phase anchored at sample 0, +accumulating forward) and lets us reason precisely about the frequency mapping. +""" + +from __future__ import annotations + +import unittest + +import numpy as np + +from python_app.hardware_full.kamil_adc.processing import ( + KamilAdcProcessingParams, + KamilAdcSweepProcessor, +) + + +def _reference(phase: np.ndarray, amplitude: float = 1000.0) -> np.ndarray: + """A reference signal whose unwrapped phase equals ``phase`` (radians).""" + return amplitude * np.exp(1j * np.asarray(phase, dtype=np.float64)) + + +# Calibration shared by the processor tests: phase 0 rad -> 2 GHz, phase 100 rad +# -> 4 GHz (slope 2e7 Hz/rad). Band [2.5, 3.5] GHz corresponds to phase [25, 75]. +_CALIBRATION = dict( + phase0_rad=0.0, + freq0_hz=2_000_000_000.0, + phase1_rad=100.0, + freq1_hz=4_000_000_000.0, + band_start_hz=2_500_000_000.0, + band_stop_hz=3_500_000_000.0, + band_points=11, +) + + +class ProcessingParamsTest(unittest.TestCase): + def _params(self, **overrides: float) -> KamilAdcProcessingParams: + return KamilAdcProcessingParams(**{**_CALIBRATION, **overrides}) # type: ignore[arg-type] + + def test_slope_is_constant_from_anchors(self) -> None: + self.assertAlmostEqual(self._params().hz_per_rad, 2.0e7) + + def test_rejects_equal_phase_anchors(self) -> None: + with self.assertRaisesRegex(ValueError, "distinct phases"): + self._params(phase0_rad=5.0, phase1_rad=5.0) + + def test_rejects_equal_frequency_anchors(self) -> None: + with self.assertRaisesRegex(ValueError, "distinct frequencies"): + self._params(freq0_hz=3.0e9, freq1_hz=3.0e9) + + def test_rejects_inverted_band(self) -> None: + with self.assertRaisesRegex(ValueError, "stop_hz must be greater"): + self._params(band_start_hz=4.0e9, band_stop_hz=3.0e9) + + def test_rejects_degenerate_point_count(self) -> None: + with self.assertRaisesRegex(ValueError, "points must be >= 2"): + self._params(band_points=1) + + +class SweepProcessorTest(unittest.TestCase): + def _processor(self, **overrides: float) -> KamilAdcSweepProcessor: + return KamilAdcSweepProcessor(KamilAdcProcessingParams(**{**_CALIBRATION, **overrides})) # type: ignore[arg-type] + + def test_grid_is_fixed_and_identical_across_calls(self) -> None: + processor = self._processor() + grid = processor.grid_hz + self.assertEqual(grid.shape, (11,)) + self.assertAlmostEqual(float(grid[0]), 2.5e9) + self.assertAlmostEqual(float(grid[-1]), 3.5e9) + np.testing.assert_array_equal(grid, processor.grid_hz) + + def test_frequency_axis_follows_calibration_law(self) -> None: + processor = self._processor() + # A dense ramp 0 -> 50 rad: endpoints map to 2 GHz and 3 GHz. + ref = _reference(np.linspace(0.0, 50.0, 201)) + freqs = processor.reference_frequency_axis(ref) + self.assertAlmostEqual(float(freqs[0]), 2.0e9, delta=1.0) + self.assertAlmostEqual(float(freqs[-1]), 3.0e9, delta=1.0) + + def test_uses_config_constants_not_sweep_endpoints(self) -> None: + """Two sweeps with different phase spans map a given absolute phase to the + SAME frequency — proving fixed config anchors, not endpoint normalization.""" + processor = self._processor() + ref_short = _reference(np.linspace(0.0, 100.0, 401)) # spans phase [0, 100] + ref_long = _reference(np.linspace(0.0, 130.0, 521)) # spans phase [0, 130] + # Encode S(f) = (f - 3 GHz) / 1 GHz, a line in TRUE frequency. + line = lambda ref: ((processor.reference_frequency_axis(ref) - 3.0e9) / 1.0e9) * np.abs(ref) + s_short = processor.process(line(ref_short), ref_short) + s_long = processor.process(line(ref_long), ref_long) + self.assertIsNotNone(s_short) + self.assertIsNotNone(s_long) + expected = (processor.grid_hz.astype(np.float64) - 3.0e9) / 1.0e9 + # Endpoint normalization would compress the longer sweep's axis and break this. + np.testing.assert_allclose(s_short.real, expected, atol=1e-3) + np.testing.assert_allclose(s_long.real, expected, atol=1e-3) + np.testing.assert_allclose(s_short.imag, 0.0, atol=1e-3) + + def test_amplitude_normalization_divides_by_reference_magnitude(self) -> None: + processor = self._processor() + ref = _reference(np.linspace(0.0, 100.0, 401), amplitude=4.0) + # |main| = 8 everywhere -> |S| = 8 / 4 = 2. + main = 8.0 * np.exp(1j * np.angle(ref)) + result = processor.process(main, ref) + self.assertIsNotNone(result) + np.testing.assert_allclose(np.abs(result), 2.0, atol=1e-3) + + def test_rejects_sweep_that_does_not_cover_band(self) -> None: + processor = self._processor() + # Phase [0, 40] -> freq [2.0, 2.8] GHz, short of the 3.5 GHz band stop. + ref = _reference(np.linspace(0.0, 40.0, 201)) + self.assertIsNone(processor.process(np.ones(201, dtype=np.complex128), ref)) + + def test_accepts_sweep_that_covers_band(self) -> None: + processor = self._processor() + ref = _reference(np.linspace(0.0, 100.0, 401)) # freq [2.0, 4.0] GHz + result = processor.process(np.abs(ref).astype(np.complex128), ref) + self.assertIsNotNone(result) + self.assertEqual(result.shape, (11,)) + self.assertEqual(result.dtype, np.complex64) + np.testing.assert_allclose(np.abs(result), 1.0, atol=1e-3) # main=|ref| -> |S|=1 + + def test_interpolates_linear_trace_onto_grid(self) -> None: + processor = self._processor() + ref = _reference(np.linspace(0.0, 100.0, 401)) + freqs = processor.reference_frequency_axis(ref) + # S(f) = (f - 2.5 GHz) / 1 GHz -> must resample to that same line on the grid. + main = ((freqs - 2.5e9) / 1.0e9) * np.abs(ref) + result = processor.process(main, ref) + self.assertIsNotNone(result) + expected = (processor.grid_hz.astype(np.float64) - 2.5e9) / 1.0e9 + np.testing.assert_allclose(result.real, expected, atol=1e-3) + np.testing.assert_allclose(result.imag, 0.0, atol=1e-3) + + def test_handles_descending_phase_direction(self) -> None: + # phase 0 -> 2 GHz, phase -100 -> 4 GHz (negative slope). Phase ramp + # 0 -> -100 therefore sweeps frequency UP across the band. + processor = self._processor(phase1_rad=-100.0) + ref = _reference(np.linspace(0.0, -100.0, 401)) + result = processor.process(np.abs(ref).astype(np.complex128), ref) + self.assertIsNotNone(result) + self.assertEqual(result.shape, (11,)) + np.testing.assert_allclose(np.abs(result), 1.0, atol=1e-3) + + def test_rejects_too_few_points(self) -> None: + processor = self._processor() + one = np.ones(1, dtype=np.complex128) + self.assertIsNone(processor.process(one, one)) + + def test_rejects_length_mismatch(self) -> None: + processor = self._processor() + self.assertIsNone( + processor.process(np.ones(10, dtype=np.complex128), np.ones(9, dtype=np.complex128)) + ) + + def test_drops_zero_amplitude_reference_points(self) -> None: + processor = self._processor() + ref = _reference(np.linspace(0.0, 100.0, 401)) + main = np.abs(ref).astype(np.complex128) + ref[10] = 0.0 # vanished reference samples must be ignored, not crash + ref[200] = 0.0 + result = processor.process(main, ref) + self.assertIsNotNone(result) + self.assertTrue(np.all(np.isfinite(result.real))) + self.assertTrue(np.all(np.isfinite(result.imag))) + + +if __name__ == "__main__": + unittest.main() diff --git a/python_app/tests/test_kamil_adc_protocol.py b/python_app/tests/test_kamil_adc_protocol.py new file mode 100644 index 0000000..90e0f8b --- /dev/null +++ b/python_app/tests/test_kamil_adc_protocol.py @@ -0,0 +1,152 @@ +"""Tests for the Kamil ADC TTY wire-protocol parser.""" + +from __future__ import annotations + +import struct +import unittest + +import numpy as np + +from python_app.hardware_full.kamil_adc.protocol import ( + MAIN_MARKER, + REFERENCE_MARKER, + KamilAdcStreamParser, +) + + +def _boundary() -> bytes: + return struct.pack(" bytes: + return struct.pack(" bytes: + return struct.pack(" None: + parser = KamilAdcStreamParser() + stream = ( + _boundary() + + _main(1, 10, -1) + _reference(1, 100, 5) + + _main(2, 20, -2) + _reference(2, 200, 6) + + _boundary() + ) + sweeps = parser.feed(stream) + self.assertEqual(len(sweeps), 1) + sweep = sweeps[0] + self.assertEqual(sweep.steps.tolist(), [1, 2]) + self.assertEqual(sweep.main.tolist(), [complex(10, -1), complex(20, -2)]) + self.assertEqual(sweep.reference.tolist(), [complex(100, 5), complex(200, 6)]) + + def test_keeps_only_steps_present_in_both_channels(self) -> None: + parser = KamilAdcStreamParser() + stream = ( + _boundary() + + _main(1, 10, 0) # main only -> dropped + + _main(2, 20, 0) + _reference(2, 200, 0) # both -> kept + + _reference(3, 300, 0) # reference only -> dropped + + _boundary() + ) + (sweep,) = parser.feed(stream) + self.assertEqual(sweep.steps.tolist(), [2]) + self.assertEqual(sweep.main.tolist(), [complex(20, 0)]) + self.assertEqual(sweep.reference.tolist(), [complex(200, 0)]) + + def test_orders_by_ascending_step_regardless_of_arrival(self) -> None: + parser = KamilAdcStreamParser() + stream = ( + _boundary() + + _main(3, 3, 0) + _reference(3, 30, 0) + + _main(1, 1, 0) + _reference(1, 10, 0) + + _main(2, 2, 0) + _reference(2, 20, 0) + + _boundary() + ) + (sweep,) = parser.feed(stream) + self.assertEqual(sweep.steps.tolist(), [1, 2, 3]) + self.assertEqual(sweep.main.real.tolist(), [1, 2, 3]) + + def test_discards_preroll_before_first_boundary(self) -> None: + parser = KamilAdcStreamParser() + # Garbage + a partial point before the first real boundary must be skipped. + stream = ( + _main(7, 7, 7) # pre-roll point (no preceding boundary) -> ignored + + _boundary() + + _main(1, 11, 0) + _reference(1, 1, 0) + + _boundary() + ) + (sweep,) = parser.feed(stream) + self.assertEqual(sweep.steps.tolist(), [1]) + + def test_handles_chunk_splits_across_frames(self) -> None: + parser = KamilAdcStreamParser() + stream = ( + _boundary() + + _main(1, 10, 0) + _reference(1, 100, 0) + + _main(2, 20, 0) + _reference(2, 200, 0) + + _boundary() + ) + sweeps: list = [] + # Feed one byte at a time to exercise reassembly across feed() calls. + for byte in stream: + sweeps.extend(parser.feed(bytes([byte]))) + self.assertEqual(len(sweeps), 1) + self.assertEqual(sweeps[0].steps.tolist(), [1, 2]) + + def test_multiple_sweeps_in_one_feed(self) -> None: + parser = KamilAdcStreamParser() + stream = ( + _boundary() + + _main(1, 1, 0) + _reference(1, 10, 0) + + _boundary() + + _main(1, 2, 0) + _reference(1, 20, 0) + + _boundary() + ) + sweeps = parser.feed(stream) + self.assertEqual(len(sweeps), 2) + self.assertEqual(sweeps[0].main.real.tolist(), [1]) + self.assertEqual(sweeps[1].main.real.tolist(), [2]) + + def test_empty_sweep_between_boundaries_is_skipped(self) -> None: + parser = KamilAdcStreamParser() + stream = _boundary() + _boundary() + _main(1, 5, 0) + _reference(1, 1, 0) + _boundary() + sweeps = parser.feed(stream) + self.assertEqual(len(sweeps), 1) + self.assertEqual(sweeps[0].steps.tolist(), [1]) + + def test_corrupt_marker_raises(self) -> None: + parser = KamilAdcStreamParser() + corrupt = struct.pack(" None: + parser = KamilAdcStreamParser() + self.assertEqual(parser.feed(_boundary() + _main(1, 1, 0)[:5]), []) + # Supply the rest of the frame plus its reference and the closing boundary. + rest = _main(1, 1, 0)[5:] + (sweep,) = parser.feed(rest + _reference(1, 9, 0) + _boundary()) + self.assertEqual(sweep.steps.tolist(), [1]) + + def test_reset_clears_state(self) -> None: + parser = KamilAdcStreamParser() + parser.feed(_boundary() + _main(1, 1, 0)) + parser.reset() + # After reset we must re-align on a fresh boundary before collecting. + sweeps = parser.feed(_main(9, 9, 0) + _boundary() + _main(1, 2, 0) + _reference(1, 2, 0) + _boundary()) + self.assertEqual(len(sweeps), 1) + self.assertEqual(sweeps[0].main.real.tolist(), [2]) + + def test_dtypes(self) -> None: + parser = KamilAdcStreamParser() + (sweep,) = parser.feed(_boundary() + _main(1, 1, 2) + _reference(1, 3, 4) + _boundary()) + self.assertEqual(sweep.main.dtype, np.complex64) + self.assertEqual(sweep.reference.dtype, np.complex64) + self.assertEqual(sweep.steps.dtype, np.int32) + + +if __name__ == "__main__": + unittest.main() diff --git a/python_app/tests/test_kamil_adc_service.py b/python_app/tests/test_kamil_adc_service.py index ed0c66f..21666c9 100644 --- a/python_app/tests/test_kamil_adc_service.py +++ b/python_app/tests/test_kamil_adc_service.py @@ -1,4 +1,4 @@ -"""Tests for Kamil ADC config, frame parsing, TTY reader, and producer wiring.""" +"""Tests for the Kamil ADC TTY reader, config round-trip, and producer wiring.""" from __future__ import annotations @@ -7,40 +7,30 @@ import os from pathlib import Path import pty import struct +import subprocess import sys import tempfile import time import tty import unittest +from unittest import mock -from python_app.hardware_full.kamil_adc_service import ( - KamilAdcTtyReader, - _parse_point_frame, -) +from python_app.hardware_full.kamil_adc import KamilAdcService, KamilAdcTtyReader +from python_app.hardware_full.kamil_adc.protocol import MAIN_MARKER, REFERENCE_MARKER from python_app.models.run_config_model import RunConfigModel from python_app.orchestration.process_supervisor import ProcessSupervisor -def _start_frame() -> bytes: - return struct.pack(" bytes: + return struct.pack(" bytes: - return struct.pack(" bytes: + return struct.pack(" None: - value = _parse_point_frame(_point_frame(1, 123, -45), expected_step=1) - self.assertEqual(value, complex(123, -45)) - - def test_rejects_bad_marker(self) -> None: - with self.assertRaisesRegex(ValueError, "marker mismatch"): - _parse_point_frame(_point_frame(1, 10, 20, marker=0x001A), expected_step=1) - - def test_rejects_wrong_step(self) -> None: - with self.assertRaisesRegex(ValueError, "step mismatch"): - _parse_point_frame(_point_frame(2, 10, 20), expected_step=1) +def _reference(step: int, real: int, imag: int) -> bytes: + return struct.pack(" None: + def test_publishes_sweep_with_aligned_main_and_reference(self) -> None: master_fd, slave_fd, reader = self._open_pty_reader() try: os.write( master_fd, - _start_frame() - + _point_frame(1, 10, -1) - + _point_frame(2, -20, 2) - + _start_frame(), + _boundary() + + _main(1, 10, -1) + _reference(1, 100, 5) + + _main(2, -20, 2) + _reference(2, 200, 6) + + _boundary(), ) - values = reader.read_sweep(timeout_s=1.0) - self.assertEqual(values.tolist(), [complex(10, -1), complex(-20, 2)]) - self.assertEqual(reader.locked_points, 2) + sweep = reader.read_sweep(timeout_s=1.0) + self.assertEqual(sweep.steps.tolist(), [1, 2]) + self.assertEqual(sweep.main.tolist(), [complex(10, -1), complex(-20, 2)]) + self.assertEqual(sweep.reference.tolist(), [complex(100, 5), complex(200, 6)]) finally: self._close(master_fd, slave_fd, reader) - def test_consecutive_constant_length_sweeps(self) -> None: - """Each newly-completed sweep is delivered once new data arrives after a read.""" + def test_variable_length_sweeps_are_allowed(self) -> None: + """Unlike the old format, sweep length may vary — no locking, just resample later.""" master_fd, slave_fd, reader = self._open_pty_reader() try: - os.write( - master_fd, - _start_frame() - + _point_frame(1, 10, -1) - + _point_frame(2, -20, 2) - + _start_frame(), - ) + # One complete sweep per write, read between, so delivery is deterministic + # (the reader publishes only the latest, overwriting unread sweeps). + os.write(master_fd, _boundary() + _main(1, 1, 0) + _reference(1, 9, 0) + _boundary()) first = reader.read_sweep(timeout_s=1.0) - self.assertEqual(first.tolist(), [complex(10, -1), complex(-20, 2)]) + self.assertEqual(first.steps.tolist(), [1]) os.write( master_fd, - _point_frame(1, 30, -3) + _point_frame(2, -40, 4) + _start_frame(), + _main(1, 2, 0) + _reference(1, 8, 0) + + _main(2, 3, 0) + _reference(2, 7, 0) + + _boundary(), ) second = reader.read_sweep(timeout_s=1.0) - self.assertEqual(second.tolist(), [complex(30, -3), complex(-40, 4)]) + self.assertEqual(second.steps.tolist(), [1, 2]) finally: self._close(master_fd, slave_fd, reader) - def test_shorter_sweep_after_lock_raises(self) -> None: - """A later sweep with fewer points than the locked-in count fails fast.""" + def test_only_latest_sweep_is_published(self) -> None: master_fd, slave_fd, reader = self._open_pty_reader() try: - os.write( - master_fd, - _start_frame() - + _point_frame(1, 10, -1) - + _point_frame(2, -20, 2) - + _start_frame() - + _point_frame(1, 30, -3) - + _start_frame(), + payload = ( + _boundary() + _main(1, 1, 0) + _reference(1, 1, 0) + + _boundary() + _main(1, 2, 0) + _reference(1, 2, 0) + + _boundary() + _main(1, 3, 0) + _reference(1, 3, 0) + + _boundary() ) - first = reader.read_sweep(timeout_s=1.0) - self.assertEqual(first.tolist(), [complex(10, -1), complex(-20, 2)]) - with self.assertRaisesRegex(RuntimeError, "sweep length changed"): - reader.read_sweep(timeout_s=1.0) + os.write(master_fd, payload) + deadline = time.monotonic() + 1.0 + while time.monotonic() < deadline and reader.published_count < 3: + time.sleep(0.005) + self.assertGreaterEqual(reader.published_count, 3) + sweep = reader.read_sweep(timeout_s=1.0) + self.assertEqual(sweep.main.tolist(), [complex(3, 0)]) finally: self._close(master_fd, slave_fd, reader) - def test_longer_sweep_after_lock_raises(self) -> None: - """A later sweep with more points than the locked-in count fails fast.""" + def test_corrupt_frame_fails_fast(self) -> None: master_fd, slave_fd, reader = self._open_pty_reader() try: - os.write( - master_fd, - _start_frame() - + _point_frame(1, 10, -1) - + _start_frame() - + _point_frame(1, 30, -3) - + _point_frame(2, -40, 4) - + _start_frame(), - ) - first = reader.read_sweep(timeout_s=1.0) - self.assertEqual(first.tolist(), [complex(10, -1)]) - with self.assertRaisesRegex(RuntimeError, "exceeded locked point count"): - reader.read_sweep(timeout_s=1.0) - finally: - self._close(master_fd, slave_fd, reader) - - def test_corrupt_frame_fails_fast_without_resync(self) -> None: - """A garbage frame (bad marker) mid-stream surfaces on read; the reader does - NOT silently resync — fail-fast lets the producer die and the supervisor relaunch.""" - master_fd, slave_fd, reader = self._open_pty_reader() - try: - os.write( - master_fd, - _start_frame() - + _point_frame(1, 10, -1) - + _point_frame(2, 5, 5, marker=0x001A) # corrupt marker (not 0x000A) - + _start_frame(), - ) + corrupt = struct.pack(" None: master_fd, slave_fd, reader = self._open_pty_reader() try: - # Start marker plus a partial sweep with no follow-up boundary. - os.write(master_fd, _start_frame() + _point_frame(1, 10, -1)) + os.write(master_fd, _boundary() + _main(1, 10, -1) + _reference(1, 1, 0)) with self.assertRaisesRegex(TimeoutError, "Timed out waiting for Kamil ADC sweep"): reader.read_sweep(timeout_s=0.1) finally: self._close(master_fd, slave_fd, reader) - def test_only_latest_sweep_is_published(self) -> None: - """If multiple sweeps arrive before the consumer reads, only the newest survives.""" - master_fd, slave_fd, reader = self._open_pty_reader() - try: - payload = ( - _start_frame() - + _point_frame(1, 1, 0) - + _point_frame(2, 2, 0) - + _start_frame() - + _point_frame(1, 3, 0) - + _point_frame(2, 4, 0) - + _start_frame() - + _point_frame(1, 5, 0) - + _point_frame(2, 6, 0) - + _start_frame() - ) - os.write(master_fd, payload) - # Wait until the reader thread has parsed all three sweeps before - # reading from the mailbox — otherwise we'd race the producer and - # might consume an intermediate value. - deadline = time.monotonic() + 1.0 - while time.monotonic() < deadline and reader.published_count < 3: - time.sleep(0.005) - self.assertGreaterEqual(reader.published_count, 3) - values = reader.read_sweep(timeout_s=1.0) - # The reader thread overwrites unread sweeps; the consumer sees the - # most recently completed one. - self.assertEqual(values.tolist(), [complex(5, 0), complex(6, 0)]) - finally: - self._close(master_fd, slave_fd, reader) - class KamilAdcConfigTest(unittest.TestCase): def test_config_round_trip_preserves_kamil_sections(self) -> None: @@ -207,85 +136,84 @@ class KamilAdcConfigTest(unittest.TestCase): "serial": "kamil_adc", "driver_mode": "native", "kamil_adc": { - "project_dir": "/home/europa/Documents/kamil_adc", - "executable_path": "/home/europa/Documents/kamil_adc/kamil_adc_capture", + "project_dir": "", + "executable_path": "build/bin/kamil_adc_collector", "tty_path": "/tmp/ttyADC_data", - "args": ["profile:phase", "do1_pair_subtract_avg"], + "args": ["profile:phase", "do8_freq_ref"], "env": {"ADC_ENV": "1"}, "startup_timeout_s": 7.0, "sweep_timeout_s": 8.0, "stop_timeout_s": 3.0, - }, - "laser_control": { - "enabled": True, - "port": "/dev/ttyUSB0", - "mode": "variation", - "pi_coeff1_p": 2560, - "pi_coeff1_i": 128, - "pi_coeff2_p": 2600, - "pi_coeff2_i": 140, - "manual": { - "temp1": 26.0, - "temp2": 27.0, - "current1": 31.0, - "current2": 32.0, + "phase_calibration": { + "phase0_rad": 1.5, + "freq0_hz": 2_046_000_000.0, + "phase1_rad": 301.0, + "freq1_hz": 5_612_000_000.0, }, - "variation": { - "variation_type": "CHANGE_CURRENT_LD2", - "static_temp1": 28.0, - "static_temp2": 29.0, - "static_current1": 33.0, - "static_current2": 34.0, - "min_value": 30.0, - "max_value": 40.0, - "step": 0.5, - "time_step": 50, - "delay_time": 10, + "band": { + "start_hz": 2_100_000_000.0, + "stop_hz": 5_500_000_000.0, + "points": 1024, }, }, - "sweep": { - "start_hz": 1.0, - "stop_hz": 2.0, - "points": 2, - "if_bandwidth_hz": 1.0, - "stimulus_power_dbm": -10.0, - }, - }, - "switches": { - "port1": {"positions": 1}, - "port2": {"positions": 1}, + "laser_control": {"enabled": True, "port": "/dev/ttyUSB0", "mode": "manual"}, + "sweep": {"start_hz": 1.0, "stop_hz": 2.0, "points": 2}, }, + "switches": {"port1": {"positions": 1}, "port2": {"positions": 1}}, } encoded = RunConfigModel.from_dict(payload).to_dict() + kamil = encoded["radar"]["kamil_adc"] self.assertEqual(encoded["radar"]["model"], "kamil_adc") - self.assertNotIn("points", encoded["radar"]["sweep"]) - self.assertEqual(encoded["radar"]["kamil_adc"]["tty_path"], "/tmp/ttyADC_data") - self.assertEqual(encoded["radar"]["kamil_adc"]["args"], ["profile:phase", "do1_pair_subtract_avg"]) - self.assertEqual(encoded["radar"]["kamil_adc"]["env"], {"ADC_ENV": "1"}) - self.assertEqual(encoded["radar"]["laser_control"]["mode"], "variation") - self.assertEqual( - encoded["radar"]["laser_control"]["variation"]["variation_type"], - "CHANGE_CURRENT_LD2", - ) + self.assertEqual(kamil["executable_path"], "build/bin/kamil_adc_collector") + self.assertEqual(kamil["args"], ["profile:phase", "do8_freq_ref"]) + self.assertEqual(kamil["phase_calibration"]["phase0_rad"], 1.5) + self.assertEqual(kamil["phase_calibration"]["freq1_hz"], 5_612_000_000.0) + self.assertEqual(kamil["band"], {"start_hz": 2_100_000_000.0, "stop_hz": 5_500_000_000.0, "points": 1024}) + + def test_close_never_raises_when_collector_refuses_to_die(self) -> None: + """close() must stay exception-safe even if a SIGKILL'd collector is not + reaped within the grace window (e.g. wedged in USB D-state).""" + with tempfile.TemporaryDirectory() as tmp_dir: + config = RunConfigModel.from_dict( + { + "radar": { + "model": "kamil_adc", + "driver_mode": "native", + "kamil_adc": { + "project_dir": tmp_dir, + "executable_path": "/bin/sh", # any real executable + "tty_path": "/tmp/ttyADC_test", + }, + }, + "switches": {"port1": {"positions": 1}, "port2": {"positions": 1}}, + } + ) + service = KamilAdcService(config) + + class _UnreapableProcess: + pid = 2_000_000_000 # implausible; killpg is patched out below anyway + + def poll(self) -> None: + return None # always "alive" + + def wait(self, timeout: float | None = None) -> int: + raise subprocess.TimeoutExpired(cmd="kamil_adc_collector", timeout=timeout) + + service._process = _UnreapableProcess() # type: ignore[assignment] + with mock.patch("python_app.hardware_full.kamil_adc.service.os.killpg"): + service.close() # must not raise def test_supervisor_selects_kamil_adc_producer(self) -> None: with tempfile.TemporaryDirectory() as tmp_dir: config_path = Path(tmp_dir) / "run_config.json" config_path.write_text(json.dumps({"radar": {"model": "kamil_adc"}}), encoding="utf-8") - command = ProcessSupervisor(Path("/repo"))._acquisition_command(config_path) self.assertEqual( command, - [ - sys.executable, - "-m", - "python_app.scripts.kamil_adc_raw_producer", - "--config", - str(config_path), - ], + [sys.executable, "-m", "python_app.scripts.kamil_adc_raw_producer", "--config", str(config_path)], ) diff --git a/python_app/tests/test_laser_control_protocol.py b/python_app/tests/test_laser_control_protocol.py index 14230a4..1a079be 100644 --- a/python_app/tests/test_laser_control_protocol.py +++ b/python_app/tests/test_laser_control_protocol.py @@ -8,7 +8,7 @@ from unittest.mock import patch from python_app.hardware_full.laser_control.controller import DEVICE_MAIN_MESSAGE_ID, LaserController from python_app.hardware_full.laser_control.models import VariationType from python_app.hardware_full.laser_control.protocol import Protocol, TaskType -from python_app.hardware_full.kamil_adc_service import apply_kamil_adc_laser_control +from python_app.hardware_full.kamil_adc import apply_kamil_adc_laser_control from python_app.models.run_config_model import RunConfigModel diff --git a/python_app/workflows/kamil_adc_neutral_preprocess.py b/python_app/workflows/kamil_adc_neutral_preprocess.py index 08171f5..ed5aaaa 100644 --- a/python_app/workflows/kamil_adc_neutral_preprocess.py +++ b/python_app/workflows/kamil_adc_neutral_preprocess.py @@ -7,6 +7,10 @@ import time import numpy as np +from python_app.hardware_full.kamil_adc import ( + KamilAdcProcessingParams, + KamilAdcSweepProcessor, +) from python_app.models.dataset_model import ComboKey, SweepCollection, TraceData from python_app.models.run_config_model import ComboModel, RunConfigModel @@ -15,33 +19,30 @@ logger = logging.getLogger(__name__) def build_kamil_adc_neutral_s21_sets( config: RunConfigModel, - point_count: int, ) -> tuple[SweepCollection, SweepCollection]: """Build neutral S21 calibration/reference collections for the Kamil ADC radar. The calibration uses unit S21 (1+0j) and the reference uses zero S21 across every configured combo, so applying them in the preprocessing pipeline leaves - the input S21 unchanged. Returns the ``(calibration, reference)`` collections. + the input S21 unchanged. The frequency axis is the exact acquisition grid + (``radar.kamil_adc.band``), so neutral sets line up sample-for-sample with + live sweeps. Returns the ``(calibration, reference)`` collections. """ if not config.is_kamil_adc: raise ValueError("Neutral Kamil ADC sets require radar.model='kamil_adc'") - points = int(point_count) - if points <= 0: - raise ValueError("Kamil ADC neutral set point count must be > 0") - combos = list(config.combos) if not combos: - combos = RunConfigModel.build_full_combos(config.input_switch.positions, config.output_switch.positions) + combos = RunConfigModel.build_full_combos( + config.input_switch.positions, config.output_switch.positions + ) if not combos: raise ValueError("Kamil ADC neutral sets require at least one switch combo") - frequency_hz = np.linspace( - float(config.radar.sweep.start_hz), - float(config.radar.sweep.stop_hz), - points, - dtype=np.float32, - ) + # Single source of truth for the axis: the same grid the processor emits. + processor = KamilAdcSweepProcessor(KamilAdcProcessingParams.from_kamil_model(config.radar.kamil_adc)) + frequency_hz = processor.grid_hz + now_ns = time.monotonic_ns() calibration = _neutral_collection( combos=combos, @@ -55,7 +56,9 @@ def build_kamil_adc_neutral_s21_sets( s21_value=np.complex64(0.0 + 0.0j), monotonic_ns=now_ns, ) - logger.info("Built neutral Kamil ADC S21 sets: combos=%d points=%d", len(combos), points) + logger.info( + "Built neutral Kamil ADC S21 sets: combos=%d points=%d", len(combos), int(frequency_hz.size) + ) return calibration, reference @@ -66,17 +69,16 @@ def _neutral_collection( s21_value: np.complex64, monotonic_ns: int, ) -> SweepCollection: - traces = [] - for combo in combos: - point_count = int(frequency_hz.size) - traces.append( - TraceData( - combo=ComboKey(input=int(combo.input), output=int(combo.output)), - frequency_hz=frequency_hz.copy(), - s11=np.zeros(point_count, dtype=np.complex64), - s21=np.full(point_count, s21_value, dtype=np.complex64), - ) + point_count = int(frequency_hz.size) + traces = [ + TraceData( + combo=ComboKey(input=int(combo.input), output=int(combo.output)), + frequency_hz=frequency_hz.copy(), + s11=np.zeros(point_count, dtype=np.complex64), + s21=np.full(point_count, s21_value, dtype=np.complex64), ) + for combo in combos + ] return SweepCollection( collection_id=1, monotonic_ns=monotonic_ns, diff --git a/python_app/workflows/multi_radar_capture_workflow.py b/python_app/workflows/multi_radar_capture_workflow.py index 3f4b7ae..3796c99 100644 --- a/python_app/workflows/multi_radar_capture_workflow.py +++ b/python_app/workflows/multi_radar_capture_workflow.py @@ -115,28 +115,8 @@ class MultiRadarSequentialCaptureSession: self._output_switch = None else: self._radar = create_single_radar_service(base_config) - self._input_switch = SwitchService( - name=base_config.input_switch.name, - positions=base_config.input_switch.positions, - default_position=base_config.input_switch.default_position, - mode=base_config.input_switch.driver_mode, - driver=base_config.input_switch.driver, - gpio_chip=base_config.input_switch.gpio_chip, - pin_a=base_config.input_switch.pin_a, - pin_b=base_config.input_switch.pin_b, - invert_logic=base_config.input_switch.invert_logic, - ) - self._output_switch = SwitchService( - name=base_config.output_switch.name, - positions=base_config.output_switch.positions, - default_position=base_config.output_switch.default_position, - mode=base_config.output_switch.driver_mode, - driver=base_config.output_switch.driver, - gpio_chip=base_config.output_switch.gpio_chip, - pin_a=base_config.output_switch.pin_a, - pin_b=base_config.output_switch.pin_b, - invert_logic=base_config.output_switch.invert_logic, - ) + self._input_switch = SwitchService.from_model(base_config.input_switch) + self._output_switch = SwitchService.from_model(base_config.output_switch) @property def kind(self) -> str: diff --git a/python_app/workflows/sequential_capture_workflow.py b/python_app/workflows/sequential_capture_workflow.py index 7e69df8..1a1beb5 100644 --- a/python_app/workflows/sequential_capture_workflow.py +++ b/python_app/workflows/sequential_capture_workflow.py @@ -91,28 +91,8 @@ class SequentialCaptureSession: self._output_switch = None else: self._radar = create_single_radar_service(config) - self._input_switch = SwitchService( - name=config.input_switch.name, - positions=config.input_switch.positions, - default_position=config.input_switch.default_position, - mode=config.input_switch.driver_mode, - driver=config.input_switch.driver, - gpio_chip=config.input_switch.gpio_chip, - pin_a=config.input_switch.pin_a, - pin_b=config.input_switch.pin_b, - invert_logic=config.input_switch.invert_logic, - ) - self._output_switch = SwitchService( - name=config.output_switch.name, - positions=config.output_switch.positions, - default_position=config.output_switch.default_position, - mode=config.output_switch.driver_mode, - driver=config.output_switch.driver, - gpio_chip=config.output_switch.gpio_chip, - pin_a=config.output_switch.pin_a, - pin_b=config.output_switch.pin_b, - invert_logic=config.output_switch.invert_logic, - ) + self._input_switch = SwitchService.from_model(config.input_switch) + self._output_switch = SwitchService.from_model(config.output_switch) @property def kind(self) -> str: diff --git a/run_config_examples/run_config_kamil_adc.example.json b/run_config_examples/run_config_kamil_adc.example.json index f64c56f..bcc5d65 100644 --- a/run_config_examples/run_config_kamil_adc.example.json +++ b/run_config_examples/run_config_kamil_adc.example.json @@ -13,14 +13,39 @@ "recovery_attempts": 3 }, "kamil_adc": { - "project_dir": "/home/europa/Documents/kamil_adc", - "executable_path": "/home/europa/Documents/kamil_adc/run_do1_pair_subtract_avg.sh", + "project_dir": "", + "executable_path": "build/bin/kamil_adc_collector", "tty_path": "/tmp/ttyADC_data", - "args": [], + "args": [ + "profile:phase", + "clock:internal", + "internal_ref_hz:2000000", + "start:di_syn2_rise", + "stop:di_syn2_fall", + "sample_clock_hz:max", + "range:2", + "duration_ms:100", + "packet_limit:0", + "do1_toggle_per_frame", + "do1_pair_subtract_avg", + "do8_freq_ref", + "do8_cycle_period:8" + ], "env": {}, "startup_timeout_s": 5.0, "sweep_timeout_s": 15.0, - "stop_timeout_s": 2.0 + "stop_timeout_s": 2.0, + "phase_calibration": { + "phase0_rad": 0.0, + "freq0_hz": 2046000000.0, + "phase1_rad": 300.0, + "freq1_hz": 5612000000.0 + }, + "band": { + "start_hz": 2100000000.0, + "stop_hz": 5500000000.0, + "points": 2048 + } }, "laser_control": { "enabled": true, diff --git a/start.sh b/start.sh index 5a37096..255b25b 100755 --- a/start.sh +++ b/start.sh @@ -18,18 +18,21 @@ LOCK_FILE="/tmp/radar_system.lock" SKIP_BUILD=0 BUILD_ONLY=0 CLEAN_SHM=0 -KAMIL_ADC_MODE=0 AUTO_START=0 PRODUCER_ONLY=0 HEADLESS=0 +# Acquisition device, detected from the active run config's radar.model. Drives +# device-specific provisioning, dependencies, and which collector binary to build +# — so every device launches the same way (no per-device flags). +RADAR_MODEL="" print_usage() { cat <<'EOF' Usage: ./start.sh [options] Options: - --kamil-adc Use the Raspberry Pi Kamil ADC profile - --profile PATH Use a specific GUI/run config profile + --profile PATH Use a specific GUI/run config profile (device is auto-detected + from its radar.model) --auto-start Start the GUI pipeline automatically after launch --headless Run without a display (Qt offscreen platform) and apply the active radar config, then start the pipeline. Suitable @@ -55,9 +58,6 @@ parse_args() { --clean-shm) CLEAN_SHM=1 ;; - --kamil-adc) - KAMIL_ADC_MODE=1 - ;; --profile) if (($# < 2)); then echo "--profile requires a path argument." >&2 @@ -100,16 +100,6 @@ absolute_path() { } resolve_profile_path() { - if ((KAMIL_ADC_MODE == 1)); then - if [[ -z "${PROFILE_PATH}" ]]; then - if [[ -f "${PROJECT_ROOT}/run_config_kamil_adc.pi.json" ]]; then - PROFILE_PATH="${PROJECT_ROOT}/run_config_kamil_adc.pi.json" - else - PROFILE_PATH="${PROJECT_ROOT}/run_config_kamil_adc.example.json" - fi - fi - fi - if [[ -n "${PROFILE_PATH}" ]]; then PROFILE_PATH="$(absolute_path "${PROFILE_PATH}")" if [[ ! -f "${PROFILE_PATH}" ]]; then @@ -119,6 +109,44 @@ resolve_profile_path() { fi } +# Resolve the config that will actually be used (explicit --profile, else the +# active run_config.json) and read its radar.model. Best-effort: any failure +# falls back to 'librevna' (the full-provisioning superset), so detection can +# never make a launch less safe. Uses system python3 (the venv may not exist yet). +detect_radar_model() { + local config_path="${PROFILE_PATH:-${PROJECT_ROOT}/run_config.json}" + RADAR_MODEL="librevna" + [[ -f "${config_path}" ]] || return + local detected + detected="$(python3 -c ' +import json, sys +try: + with open(sys.argv[1]) as handle: + data = json.load(handle) + radar = data.get("radar") if isinstance(data, dict) else {} + model = radar.get("model") if isinstance(radar, dict) else None + print(model or "librevna") +except Exception: + print("librevna") +' "${config_path}" 2>/dev/null)" || detected="" + [[ -n "${detected}" ]] && RADAR_MODEL="${detected}" + echo "[start.sh] Detected radar model: ${RADAR_MODEL} (config: ${config_path})" +} + +# Acquisition producer for the active model, mirroring the process supervisor's +# selection so --producer-only behaves identically to a full pipeline launch. +producer_command() { + local config_path="$1" + case "${RADAR_MODEL}" in + kamil_adc) + printf '%s\0' "${PYTHON_CMD}" -m python_app.scripts.kamil_adc_raw_producer --config "${config_path}" ;; + librevna_multi|sn9000) + printf '%s\0' "${PYTHON_CMD}" -m python_app.scripts.matrix_raw_producer --config "${config_path}" ;; + *) + printf '%s\0' "${PROJECT_ROOT}/build/bin/sweep_orchestrator" --config "${config_path}" ;; + esac +} + check_environment() { if ! command -v python3 >/dev/null 2>&1; then echo "python3 is not installed or not found in PATH." >&2 @@ -138,7 +166,8 @@ check_environment() { ensure_python_dependencies() { local dependency_check - if ((KAMIL_ADC_MODE == 1)); then + if [[ "${RADAR_MODEL}" == "kamil_adc" ]]; then + # Kamil ADC has no VISA dependency (it talks to its own L-Card collector). dependency_check='import numpy, serial, PyQt6, pyqtgraph, usb1' else dependency_check='import numpy, serial, PyQt6, pyqtgraph, usb1, pyvisa' @@ -260,14 +289,20 @@ EOF } build_cpp_binaries() { - if make -C "${PROJECT_ROOT}" -q all >/dev/null 2>&1; then + local jobs + jobs="${BUILD_JOBS:-$(nproc)}" + # The Kamil ADC collector is an extra, device-specific binary built only for + # that model; all models share the core pipeline binaries (`all`). + local targets="all" + if [[ "${RADAR_MODEL}" == "kamil_adc" ]]; then + targets="all kamil_adc_collector" + fi + if make -C "${PROJECT_ROOT}" -q ${targets} >/dev/null 2>&1; then echo "[start.sh] C++ binaries are up to date; skipping build." return fi - local jobs - jobs="${BUILD_JOBS:-$(nproc)}" - echo "[start.sh] Building C++ binaries (jobs=${jobs})..." - make -C "${PROJECT_ROOT}" -j"${jobs}" all + echo "[start.sh] Building C++ binaries (jobs=${jobs}, targets: ${targets})..." + make -C "${PROJECT_ROOT}" -j"${jobs}" ${targets} } cleanup_known_shm() { @@ -286,6 +321,17 @@ cleanup_known_shm() { || true } +kill_stale_adc_collector() { + # The L-Card ADC collector runs in its own session and can outlive a crashed + # or force-killed run, holding the E-502 device and hanging the next start. + # Kill any leftover hard before launching so acquisition always opens cleanly. + if command -v pkill >/dev/null 2>&1; then + if pkill -9 -f kamil_adc_collector 2>/dev/null; then + echo "[start.sh] Killed leftover ADC collector process(es)." + fi + fi +} + run_gui() { if [[ -n "${PROFILE_PATH}" ]]; then export RADAR_SYSTEM_PROFILE="${PROFILE_PATH}" @@ -311,18 +357,20 @@ run_gui() { } run_producer_only() { - if ((KAMIL_ADC_MODE != 1)); then - echo "--producer-only currently requires --kamil-adc." >&2 - exit 1 - fi - if [[ -z "${PROFILE_PATH}" ]]; then - echo "--producer-only requires a resolved config profile." >&2 + local config_path="${PROFILE_PATH:-${PROJECT_ROOT}/run_config.json}" + if [[ ! -f "${config_path}" ]]; then + echo "--producer-only needs a config (pass --profile, or create run_config.json)." >&2 exit 1 fi export PYTHONPATH="${PROJECT_ROOT}${PYTHONPATH:+:${PYTHONPATH}}" - echo "[start.sh] Starting Kamil ADC raw producer with profile: ${PROFILE_PATH}" - exec "${PYTHON_CMD}" -m python_app.scripts.kamil_adc_raw_producer --config "${PROFILE_PATH}" + # Run the acquisition producer the supervisor would pick for this model. + local -a command=() + while IFS= read -r -d '' token; do + command+=("${token}") + done < <(producer_command "${config_path}") + echo "[start.sh] Starting ${RADAR_MODEL} producer: ${command[*]}" + exec "${command[@]}" } stop_headless_service() { @@ -345,7 +393,13 @@ verify_cpp_binaries() { # Guard the daemon's --skip-build path: refuse to run against a tree whose C++ # binaries were never built, instead of failing obscurely at spawn time. local missing=0 bin - for bin in data_processor data_preprocessor; do + local required_bins="data_processor data_preprocessor" + # Kamil ADC also needs its acquisition collector; other models use the C++ + # sweep_orchestrator, which `all` always builds. + if [[ "${RADAR_MODEL}" == "kamil_adc" ]]; then + required_bins="${required_bins} kamil_adc_collector" + fi + for bin in ${required_bins}; do if [[ ! -x "${PROJECT_ROOT}/build/bin/${bin}" ]]; then echo "Required binary missing or not executable: build/bin/${bin}" >&2 missing=1 @@ -373,10 +427,13 @@ main() { parse_args "$@" resolve_profile_path check_environment + detect_radar_model # Skip first-time provisioning (build headers, USB udev rule) in headless # mode: the daemon runs unattended at boot as a non-root user and must not # block on sudo. A fresh machine is provisioned by one interactive launch. - if ((KAMIL_ADC_MODE == 0 && HEADLESS == 0)); then + # Also skip it for non-LibreVNA devices (e.g. Kamil ADC), which neither use + # libusb directly nor need the LibreVNA USB access rule. + if [[ "${RADAR_MODEL}" != "kamil_adc" ]] && ((HEADLESS == 0)); then ensure_system_dependencies ensure_usb_access_rules fi @@ -404,6 +461,8 @@ main() { stop_headless_service fi acquire_single_instance_lock + # Clear any ADC collector wedged by a previous run before we launch a new one. + kill_stale_adc_collector if ((PRODUCER_ONLY == 1)); then run_producer_only