new rezhim

This commit is contained in:
awe
2026-04-29 17:37:36 +03:00
parent 486a811f3b
commit f7971c874d
2 changed files with 255 additions and 9 deletions

231
main.cpp
View File

@ -116,6 +116,7 @@ struct Config {
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<uint32_t> noise_avg_steps;
};
@ -474,7 +475,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] [do1_noise_subtract] [do1_raw_tty_marked] [noise_avg_steps:N]\n"
<< " [do1_toggle_per_frame] [do1_noise_subtract] [do1_raw_tty_marked] [do1_pair_subtract_avg] [noise_avg_steps:N]\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"
@ -520,9 +521,13 @@ void print_help(const char* exe_name) {
<< " 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"
<< " tty fast stream-only modes -> di1_group_avg, do1_noise_subtract, do1_raw_tty_marked; skip CSV/SVG/live outputs\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"
@ -553,6 +558,8 @@ void print_help(const char* exe_name) {
<< "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"
@ -673,6 +680,10 @@ Config parse_args(int argc, char** argv) {
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;
@ -792,7 +803,7 @@ Config parse_args(int argc, char** argv) {
cfg.live_update_period_ms = std::max<uint32_t>(cfg.live_update_period_ms, 1000U);
}
}
if (cfg.tty_path && (cfg.di1_group_average || cfg.do1_noise_subtract || cfg.do1_raw_tty_marked)) {
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<uint32_t>(cfg.recv_block_words, 65536U);
}
@ -818,6 +829,9 @@ Config parse_args(int argc, char** argv) {
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:<path>");
@ -831,6 +845,9 @@ Config parse_args(int argc, char** argv) {
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");
}
@ -847,6 +864,9 @@ Config parse_args(int argc, char** argv) {
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");
}
@ -854,6 +874,26 @@ Config parse_args(int argc, char** argv) {
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:<path>");
}
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 (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");
}
@ -1407,6 +1447,105 @@ struct Do1NoiseSubtractState {
void finish_step() { clear_step(); }
};
struct Do1PairSubtractAverageState {
struct PairSample {
double ch1 = 0.0;
double ch2 = 0.0;
};
std::deque<PairSample> low_pairs;
std::deque<PairSample> 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;
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;
}
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) {
pending_ch1_valid = true;
pending_ch1_raw = raw_code;
pending_ch1_di1_high = di1_high;
pending_ch1_di2_high = di2_high;
}
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<double>(diff_count);
}
double avg_diff_ch2() const {
if (diff_count == 0U) {
return 0.0;
}
return diff_sum_ch2 / static_cast<double>(diff_count);
}
};
int16_t pack_raw_code_to_int16(double avg_raw_code) {
const double scaled =
avg_raw_code * 32767.0 / static_cast<double>(X502_ADC_SCALE_CODE_MAX);
@ -1688,15 +1827,17 @@ int run(const Config& cfg) {
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_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_di1_group_average ? std::string("di1_group_avg")
: std::string("none")));
: (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<uint64_t>(read_capacity_words) + 1U) / 2U;
const uint64_t typical_tty_batch_bytes = typical_tty_batch_frames * sizeof(uint16_t) * 4U;
@ -1707,7 +1848,8 @@ int run(const Config& cfg) {
}
tty_ring_capacity_bytes = static_cast<std::size_t>(tty_ring_bytes_u64);
tty_writer = std::make_unique<TtyProtocolWriter>(*cfg.tty_path, tty_ring_capacity_bytes);
if (!tty_di1_group_average && !tty_do1_noise_subtract && !tty_do1_raw_tty_marked) {
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);
}
}
@ -1727,10 +1869,13 @@ int run(const Config& cfg) {
<< " 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";
@ -1758,6 +1903,11 @@ int run(const Config& cfg) {
: (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";
@ -1826,6 +1976,7 @@ int run(const Config& cfg) {
TtyGroupAverageState tty_group_state;
Do1RawTaggedState tty_do1_raw_state;
Do1NoiseSubtractState tty_do1_noise_state;
Do1PairSubtractAverageState tty_do1_pair_state;
std::vector<uint16_t> tty_frame_words;
tty_frame_words.reserve(static_cast<std::size_t>(read_capacity_words) * 2U + 16U);
if (tty_do1_noise_subtract) {
@ -2041,6 +2192,32 @@ int run(const Config& cfg) {
tty_do1_noise_state.finish_step();
};
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");
}
append_tty_frame(0x000AU,
tty_do1_pair_state.next_index,
static_cast<uint16_t>(pack_raw_code_to_int16(tty_do1_pair_state.avg_diff_ch1())),
static_cast<uint16_t>(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();
};
auto flush_tty_frames = [&]() {
if (!tty_writer || tty_frame_words.empty()) {
return;
@ -2077,6 +2254,9 @@ int run(const Config& cfg) {
} 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();
}
packet_active = true;
};
@ -2100,6 +2280,9 @@ int run(const Config& cfg) {
// Finalize the last DI1 run in packet, drop only incomplete channel tuples.
append_tty_do1_subtracted_step();
tty_do1_noise_state.clear_step();
} else if (tty_do1_pair_subtract_avg) {
append_tty_do1_pair_subtracted_avg_run();
tty_do1_pair_state.reset_packet();
}
const std::size_t frames = fast_tty_avg_stream_mode
@ -2222,6 +2405,8 @@ int run(const Config& cfg) {
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();
}
};
@ -2473,7 +2658,8 @@ int run(const Config& cfg) {
continue;
}
if ((din_value & kE502Digital2Mask) != 0U) {
const bool di2_level = (din_value & kE502Digital2Mask) != 0U;
if (di2_level) {
++packet_di2_high_clocks;
} else {
++packet_di2_low_clocks;
@ -2508,7 +2694,34 @@ int run(const Config& cfg) {
if (fast_tty_avg_stream_mode) {
if (fast_packet_frames < target_frames) {
if (tty_do1_noise_subtract) {
if (tty_do1_pair_subtract_avg) {
if (lch == 0U) {
tty_do1_pair_state.add_pending_ch1(adc_raw_value, di1_level, di2_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);
if (pair_di1_matches && pair_di2_matches) {
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();
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);
}
}
tty_do1_pair_state.clear_pending_ch1();
} else {
tty_do1_pair_state.clear_pending_ch1();
}
} else 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);