From 6ea2653275e0e161c1e7832fe4c68d6d19312e6a Mon Sep 17 00:00:00 2001 From: awe Date: Fri, 10 Apr 2026 14:08:23 +0300 Subject: [PATCH] add averaging --- main.cpp | 205 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 170 insertions(+), 35 deletions(-) diff --git a/main.cpp b/main.cpp index e9d1b97..bd72654 100644 --- a/main.cpp +++ b/main.cpp @@ -101,6 +101,7 @@ struct Config { 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; }; [[noreturn]] void fail(const std::string& message) { @@ -381,7 +382,7 @@ void print_help(const char* exe_name) { << " [internal_ref_hz:2000000]\n" << " [di1:zero|trace|ignore]\n" << " [duration_ms:100] [packet_limit:0] [csv:capture.csv] [svg:capture.svg]\n" - << " [live_html:live_plot.html] [live_json:live_plot.json] [tty:/tmp/ttyADC_data]\n" + << " [live_html:live_plot.html] [live_json:live_plot.json] [tty:/tmp/ttyADC_data] [di1_group_avg]\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" @@ -413,6 +414,7 @@ void print_help(const char* exe_name) { << " step_words:32768 -> input stream transfer step in 32-bit words\n" << " live_html/live_json -> live graph files updated as packets arrive\n" << " tty:/tmp/ttyADC_data -> write a continuous legacy 4-word CH1/CH2 stream to a Linux/POSIX tty or PTY link path\n" + << " di1_group_avg -> with tty + di1:trace, emit one averaged CH1/CH2 step per constant DI1 run\n" << " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n" << "\n" << "Differential physical channel mapping:\n" @@ -432,6 +434,8 @@ void print_help(const char* exe_name) { << "can either zero ADC samples on change, be exported as a separate synchronous trace, or be ignored.\n" << "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" << "\n" << "Recommended working example:\n" << " " << exe_name @@ -441,7 +445,13 @@ void print_help(const char* exe_name) { << "Internal-clock example:\n" << " " << exe_name << " 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:chirp.csv svg:chirp.svg\n"; + << " sample_clock_hz:max duration_ms:100 packet_limit:0 csv:chirp.csv svg:chirp.svg\n" + << "\n" + << "TTY DI1-group average example:\n" + << " " << exe_name + << " 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"; } Config parse_args(int argc, char** argv) { @@ -513,6 +523,10 @@ Config parse_args(int argc, char** argv) { 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 (starts_with(arg, "sample_clock_hz:")) { const std::string value = trim_copy(arg.substr(16)); cfg.sample_clock_specified = true; @@ -631,6 +645,12 @@ Config parse_args(int argc, char** argv) { 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.tty_path && cfg.di1_group_average && (cfg.channel_count != 2U)) { + fail("tty di1_group_avg requires channels:2"); + } if (cfg.tty_path && (cfg.channel_count != 2U)) { fail("tty output requires channels:2"); } @@ -996,6 +1016,40 @@ struct TtyContinuousState { 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() const { + return (count_ch1 != 0U) && (count_ch2 != 0U); + } +}; + 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); @@ -1253,6 +1307,7 @@ int run(const Config& cfg) { 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; 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; @@ -1263,7 +1318,9 @@ int run(const Config& cfg) { } tty_ring_capacity_bytes = static_cast(tty_ring_bytes_u64); tty_writer = std::make_unique(*cfg.tty_path, tty_ring_capacity_bytes); - tty_writer->emit_packet_start(); + if (!tty_di1_group_average) { + tty_writer->emit_packet_start(); + } } CaptureFileWriter writer(cfg.csv_path, cfg.svg_path, cfg.live_html_path, cfg.live_json_path); writer.initialize_live_plot(); @@ -1276,6 +1333,11 @@ int run(const Config& cfg) { << " 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" << " SVG history packets kept in RAM: " << ((cfg.svg_history_packets == 0U) ? std::string("all (unbounded)") : std::to_string(cfg.svg_history_packets)) << "\n"; @@ -1293,12 +1355,14 @@ int run(const Config& cfg) { 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; std::vector tty_frame_words; - tty_frame_words.reserve(static_cast(read_capacity_words) * 2U); + tty_frame_words.reserve(static_cast(read_capacity_words) * 2U + 16U); current_packet.reset(target_frames, cfg.channel_count); std::size_t csv_global_frame_index = 0; @@ -1385,15 +1449,67 @@ int run(const Config& cfg) { } }; + 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(0x000A, 0xFFFF, 0xFFFF, 0xFFFF); + }; + + auto append_tty_group_step = [&]() { + if (!tty_di1_group_average || !tty_group_state.has_complete_step()) { + 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 = tty_group_state.sum_ch2 / static_cast(tty_group_state.count_ch2); + append_tty_frame(0x000A, + static_cast(tty_group_state.next_index), + static_cast(pack_raw_code_to_int16(ch1_avg)), + static_cast(pack_raw_code_to_int16(ch2_avg))); + ++tty_group_state.next_index; + }; + + 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; } current_packet.reset(target_frames, cfg.channel_count); + if (tty_di1_group_average) { + tty_group_state.reset_packet(); + append_tty_packet_start(); + } packet_active = true; }; auto finish_packet = [&](PacketCloseReason reason) { + if (tty_di1_group_average) { + append_tty_group_step(); + tty_group_state.clear_step(); + } + const std::size_t frames = current_packet.frame_count(cfg.channel_count); if (frames != 0U) { current_packet.channels[0].resize(frames); @@ -1475,6 +1591,9 @@ int run(const Config& cfg) { packet_active = false; current_packet.reset(target_frames, cfg.channel_count); + if (tty_di1_group_average) { + tty_group_state.clear_step(); + } }; while (!stop_loop_requested) { @@ -1488,6 +1607,7 @@ int run(const Config& cfg) { if (console_stop_requested()) { if (packet_active) { finish_packet(PacketCloseReason::UserStop); + flush_tty_frames(); } stop_loop_requested = true; break; @@ -1573,37 +1693,30 @@ int run(const Config& cfg) { fail("Raw ADC parsing returned a different sample count than voltage processing"); } - tty_frame_words.clear(); - tty_frame_words.reserve(((static_cast(raw_adc_count) + 1U) / 2U) * 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 (!tty_state.pending_ch1_valid) { - tty_state.pending_ch1 = sample; - tty_state.pending_ch1_valid = true; - continue; - } - - tty_frame_words.push_back(0x000A); - tty_frame_words.push_back(tty_state.next_index); - tty_frame_words.push_back(static_cast(tty_state.pending_ch1)); - tty_frame_words.push_back(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; - } - } - - if (!tty_frame_words.empty()) { - tty_writer->enqueue_encoded_frames(tty_frame_words.data(), tty_frame_words.size() / 4U); - - 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; + if (!tty_di1_group_average) { + tty_frame_words.clear(); + tty_frame_words.reserve(((static_cast(raw_adc_count) + 1U) / 2U) * 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 (!tty_state.pending_ch1_valid) { + tty_state.pending_ch1 = sample; + tty_state.pending_ch1_valid = true; + continue; + } + + append_tty_frame(0x000A, + 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(); } } @@ -1619,20 +1732,31 @@ int run(const Config& cfg) { for (uint32_t i = 0; i < adc_count; ++i) { pending_adc.push_back(adc_buffer[i]); } + if (tty_di1_group_average) { + for (uint32_t i = 0; i < raw_adc_count; ++i) { + pending_adc_raw.push_back(adc_raw_buffer[i]); + } + } for (uint32_t i = 0; i < din_count; ++i) { pending_din.push_back(din_buffer[i]); } if ((pending_adc.size() > 1000000U) || - (pending_din.size() > 1000000U)) { + (pending_din.size() > 1000000U) || + (tty_di1_group_average && (pending_adc_raw.size() > 1000000U))) { fail("Internal backlog grew too large while aligning ADC and DIN samples"); } while (!pending_adc.empty() && !pending_din.empty() && + (!tty_di1_group_average || !pending_adc_raw.empty()) && !stop_loop_requested) { const double adc_value = pending_adc.front(); pending_adc.pop_front(); + const double adc_raw_value = tty_di1_group_average ? pending_adc_raw.front() : 0.0; + if (tty_di1_group_average) { + pending_adc_raw.pop_front(); + } const uint32_t din_value = pending_din.front(); pending_din.pop_front(); @@ -1697,6 +1821,11 @@ int run(const Config& cfg) { continue; } + if (tty_di1_group_average && di1_changed) { + append_tty_group_step(); + tty_group_state.clear_step(); + } + const uint32_t lch = next_lch; next_lch = (next_lch + 1U) % cfg.channel_count; @@ -1715,6 +1844,9 @@ int run(const Config& cfg) { if (current_packet.channels[lch].size() < target_frames) { current_packet.channels[lch].push_back(stored_value); + if (tty_di1_group_average) { + tty_group_state.add_sample(lch, adc_raw_value); + } ++current_packet.stored_samples; ++total_stored_adc_samples; ++stats_stored_adc_samples; @@ -1738,10 +1870,13 @@ int run(const Config& cfg) { } } + flush_tty_frames(); + if (console_stop_requested()) { if (packet_active) { finish_packet(PacketCloseReason::UserStop); } + flush_tty_frames(); stop_loop_requested = true; }