From f462e65cc2a85ed311ee0b87413787fafe080267 Mon Sep 17 00:00:00 2001 From: awe Date: Tue, 21 Apr 2026 17:02:46 +0300 Subject: [PATCH] main - Update main.cpp --- main.cpp | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index 1d357d1..03819eb 100644 --- a/main.cpp +++ b/main.cpp @@ -113,6 +113,7 @@ struct Config { std::string live_json_path = "live_plot.json"; std::optional tty_path; bool di1_group_average = false; + bool do1_toggle_per_frame = false; }; [[noreturn]] void fail(const std::string& message) { @@ -470,6 +471,7 @@ void print_help(const char* exe_name) { << " [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]\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" @@ -504,6 +506,8 @@ void print_help(const char* exe_name) { << " live_html/live_json -> live graph files updated as packets arrive outside tty+di1_group_avg fast mode\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 -> packet-gated DO1 output: LOW,HIGH,LOW,HIGH per 2-channel frame inside packet\n" + << " outside packet windows DO1 is forced LOW\n" << " tty+di1_group_avg -> fast stream-only mode: skip CSV/SVG/live outputs and send only averaged tty data\n" << " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n" << "\n" @@ -631,6 +635,10 @@ Config parse_args(int argc, char** argv) { cfg.di1_group_average = true; continue; } + if (arg == "do1_toggle_per_frame") { + cfg.do1_toggle_per_frame = true; + continue; + } if (starts_with(arg, "sample_clock_hz:")) { const std::string value = trim_copy(arg.substr(16)); cfg.sample_clock_specified = true; @@ -765,6 +773,9 @@ Config parse_args(int argc, char** argv) { if (cfg.di1_group_average && (cfg.di1_mode != Di1Mode::Trace)) { fail("di1_group_avg requires di1:trace"); } + if (cfg.do1_toggle_per_frame && (cfg.channel_count != 2U)) { + fail("do1_toggle_per_frame requires channels:2"); + } 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"); } @@ -914,6 +925,7 @@ struct Api { decltype(&X502_SetAdcFreq) SetAdcFreq = nullptr; decltype(&X502_GetAdcFreq) GetAdcFreq = nullptr; decltype(&X502_SetDinFreq) SetDinFreq = nullptr; + decltype(&X502_SetOutFreq) SetOutFreq = nullptr; decltype(&X502_SetRefFreq) SetRefFreq = nullptr; decltype(&X502_SetStreamBufSize) SetStreamBufSize = nullptr; decltype(&X502_SetStreamStep) SetStreamStep = nullptr; @@ -924,6 +936,9 @@ struct Api { decltype(&X502_StreamsStart) StreamsStart = nullptr; decltype(&X502_GetRecvReadyCount) GetRecvReadyCount = nullptr; decltype(&X502_Recv) Recv = nullptr; + decltype(&X502_PreloadStart) PreloadStart = nullptr; + decltype(&X502_Send) Send = nullptr; + decltype(&X502_PrepareData) PrepareData = nullptr; decltype(&X502_ProcessData) ProcessData = nullptr; decltype(&E502_OpenUsb) OpenUsb = nullptr; @@ -967,6 +982,7 @@ struct Api { 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"); SetRefFreq = load_symbol(x502_module, "X502_SetRefFreq"); SetStreamBufSize = load_symbol(x502_module, "X502_SetStreamBufSize"); SetStreamStep = load_symbol(x502_module, "X502_SetStreamStep"); @@ -977,6 +993,9 @@ struct Api { StreamsStart = load_symbol(x502_module, "X502_StreamsStart"); GetRecvReadyCount = load_symbol(x502_module, "X502_GetRecvReadyCount"); Recv = load_symbol(x502_module, "X502_Recv"); + PreloadStart = load_symbol(x502_module, "X502_PreloadStart"); + 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"); @@ -1009,6 +1028,7 @@ 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 kStreamInputAdcFlag = 0x80000000U; constexpr uint32_t kStreamInputCalibratedAdcFlag = 0x40000000U; @@ -1361,9 +1381,20 @@ int run(const Config& cfg) { << " 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_frame_freq_hz <= 0.0) { + fail("do1_toggle_per_frame requires a positive ADC frame rate"); + } + 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) { @@ -1381,7 +1412,11 @@ int run(const Config& cfg) { 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.StreamsEnable(device.hnd, X502_STREAM_ADC | X502_STREAM_DIN), "Enable ADC+DIN stream"); + 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))); @@ -1413,6 +1448,10 @@ int run(const Config& cfg) { << " 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 (packet-gated, LOW-first)") : std::string("disabled")) << "\n" + << " DOUT rate: " + << (cfg.do1_toggle_per_frame ? std::to_string(actual_dout_freq_hz) + " Hz" : std::string("disabled")) << "\n" << " ADC range: +/-" << range_to_volts(cfg.range) << " V\n" << " max frames per packet per channel: " << target_frames << "\n"; @@ -1476,6 +1515,22 @@ int run(const Config& cfg) { std::cerr << "Warning: Ctrl+C handler could not be installed; continuous mode may stop abruptly.\n"; } + if (cfg.do1_toggle_per_frame) { + expect_ok(api, api.PreloadStart(device.hnd), "Start DOUT preload"); + const uint32_t preload_low_word = 0U; + uint32_t preload_encoded_word = 0U; + expect_ok(api, + api.PrepareData(device.hnd, nullptr, nullptr, &preload_low_word, 1U, 0, &preload_encoded_word), + "Prepare initial DOUT LOW"); + const int32_t preload_sent = api.Send(device.hnd, &preload_encoded_word, 1U, cfg.recv_timeout_ms); + if (preload_sent < 0) { + fail("Send initial DOUT LOW: " + x502_error(api, preload_sent)); + } + if (preload_sent != 1) { + fail("Send initial DOUT LOW did not transmit exactly one word"); + } + } + expect_ok(api, api.StreamsStart(device.hnd), "Start streams"); device.streams_started = true; @@ -1486,6 +1541,7 @@ int run(const Config& cfg) { std::deque pending_adc; std::deque pending_adc_raw; std::deque pending_din; + std::deque pending_dout; std::deque packets; PacketAccumulator current_packet; TtyContinuousState tty_state; @@ -1516,6 +1572,14 @@ int run(const Config& cfg) { TickMs stats_window_start = capture_loop_start; TickMs last_stats_print = capture_loop_start; TickMs last_live_update = 0; + bool do1_next_state_high = false; + const std::size_t max_pending_dout_words = 1000000U; + std::vector dout_prepare_input; + std::vector dout_send_words; + if (cfg.do1_toggle_per_frame) { + dout_prepare_input.resize(read_capacity_words); + dout_send_words.resize(read_capacity_words); + } uint64_t total_raw_words = 0; uint64_t total_adc_samples = 0; @@ -1632,6 +1696,66 @@ int run(const Config& cfg) { } }; + auto append_dout_word = [&](bool do1_high) { + if (!cfg.do1_toggle_per_frame) { + return; + } + pending_dout.push_back(do1_high ? kE502Do1Mask : 0U); + if (pending_dout.size() > max_pending_dout_words) { + fail("Internal DOUT backlog grew too large"); + } + }; + + auto flush_dout_queue = [&](uint32_t send_timeout_ms, bool require_full_flush) { + if (!cfg.do1_toggle_per_frame || pending_dout.empty()) { + return; + } + + while (!pending_dout.empty()) { + const std::size_t chunk_words = std::min(pending_dout.size(), dout_prepare_input.size()); + for (std::size_t i = 0; i < chunk_words; ++i) { + dout_prepare_input[i] = pending_dout[i]; + } + + expect_ok(api, + api.PrepareData(device.hnd, + nullptr, + nullptr, + dout_prepare_input.data(), + static_cast(chunk_words), + 0, + dout_send_words.data()), + "Prepare DOUT data"); + + std::size_t sent_words = 0; + while (sent_words < chunk_words) { + const int32_t sent = api.Send(device.hnd, + dout_send_words.data() + sent_words, + static_cast(chunk_words - sent_words), + send_timeout_ms); + if (sent < 0) { + fail("Send DOUT data: " + x502_error(api, sent)); + } + if (sent == 0) { + break; + } + sent_words += static_cast(sent); + } + + for (std::size_t i = 0; i < sent_words; ++i) { + pending_dout.pop_front(); + } + + if (sent_words < chunk_words) { + break; + } + } + + if (require_full_flush && !pending_dout.empty()) { + fail("DOUT queue could not be fully flushed before shutdown"); + } + }; + auto start_packet = [&]() { if (packet_active) { return; @@ -1648,6 +1772,9 @@ int run(const Config& cfg) { tty_group_state.reset_packet(); append_tty_packet_start(); } + if (cfg.do1_toggle_per_frame) { + do1_next_state_high = false; + } packet_active = true; }; @@ -2023,12 +2150,24 @@ int run(const Config& cfg) { if (packet_active && stop_edge) { finish_packet(PacketCloseReason::ExternalStopEdge); + if (cfg.do1_toggle_per_frame && (lch == (cfg.channel_count - 1U))) { + append_dout_word(false); + } if ((cfg.packet_limit != 0U) && (total_completed_packets >= cfg.packet_limit)) { stop_loop_requested = true; } continue; } + if (cfg.do1_toggle_per_frame && (lch == (cfg.channel_count - 1U))) { + if (packet_active) { + append_dout_word(do1_next_state_high); + do1_next_state_high = !do1_next_state_high; + } else { + append_dout_word(false); + } + } + if (!packet_active) { continue; } @@ -2099,6 +2238,7 @@ int run(const Config& cfg) { } } + flush_dout_queue(0U, false); flush_tty_frames(); if (console_stop_requested()) { @@ -2114,6 +2254,9 @@ int run(const Config& cfg) { } } + append_dout_word(false); + flush_dout_queue(cfg.recv_timeout_ms, true); + expect_ok(api, api.StreamsStop(device.hnd), "Stop streams"); device.streams_started = false;