Compare commits

12 Commits

Author SHA1 Message Date
awe
8175d1cba0 di 1 incre 2026-04-24 18:01:45 +03:00
awe
70b5557be9 fix 2026-04-24 17:04:13 +03:00
awe
4a0740539b noise sub mode 2026-04-24 14:10:47 +03:00
awe
bee128dec7 2 ticks upd 2026-04-24 13:52:12 +03:00
awe
94df442395 fix 2026-04-21 22:55:20 +03:00
awe
38a76ead58 fix 2026-04-21 22:15:12 +03:00
awe
deb21dc681 fix 2026-04-21 22:05:39 +03:00
awe
61ac134424 fix 2026-04-21 21:27:42 +03:00
awe
4aded6604a test new 2026-04-21 21:09:25 +03:00
awe
d36fb23470 fix 2026-04-21 20:40:17 +03:00
awe
9b9b7a79ad remove svg csv save 2026-04-21 19:36:45 +03:00
awe
4e69bb67e2 upd fix do 2026-04-21 19:34:07 +03:00
2 changed files with 361 additions and 154 deletions

470
main.cpp
View File

@ -114,6 +114,8 @@ struct Config {
std::optional<std::string> tty_path; std::optional<std::string> tty_path;
bool di1_group_average = false; bool di1_group_average = false;
bool do1_toggle_per_frame = false; bool do1_toggle_per_frame = false;
bool do1_noise_subtract = false;
std::optional<uint32_t> noise_avg_steps;
}; };
[[noreturn]] void fail(const std::string& message) { [[noreturn]] void fail(const std::string& message) {
@ -471,7 +473,7 @@ void print_help(const char* exe_name) {
<< " [di1:zero|trace|ignore]\n" << " [di1:zero|trace|ignore]\n"
<< " [duration_ms:100] [packet_limit:0] [csv:capture.csv] [svg:capture.svg]\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" << " [live_html:live_plot.html] [live_json:live_plot.json] [tty:/tmp/ttyADC_data] [di1_group_avg]\n"
<< " [do1_toggle_per_frame]\n" << " [do1_toggle_per_frame] [do1_noise_subtract] [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" << " [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" << " [buffer_words:8388608] [step_words:32768]\n"
<< " [pullup_syn1] [pullup_syn2] [pulldown_conv_in] [pulldown_start_in]\n" << " [pullup_syn1] [pullup_syn2] [pulldown_conv_in] [pulldown_start_in]\n"
@ -496,19 +498,26 @@ void print_help(const char* exe_name) {
<< " stats_period_ms:1000 -> print online transfer/capture statistics every 1000 ms (0 disables)\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" << " 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" << " live_update_period_ms:1000 -> refresh live HTML/JSON no more than once per second"
<< " (ignored in tty+di1_group_avg fast mode)\n" << " (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)" << " svg_history_packets:50 -> keep last 50 packets in RAM for final SVG (0 keeps all, risky)"
<< " (ignored in tty+di1_group_avg fast mode)\n" << " (ignored in tty fast stream-only modes)\n"
<< " duration_ms:100 -> max packet length if stop edge does not arrive\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" << " 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" << " buffer_words:8388608 -> input stream buffer size in 32-bit words\n"
<< " step_words:32768 -> input stream transfer step 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+di1_group_avg fast mode\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" << " 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" << " 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" << " do1_toggle_per_frame -> hardware cyclic DO1 pattern in module memory:\n"
<< " outside packet windows DO1 is forced LOW\n" << " DO1 outputs 00110011... continuously (toggle every 2 ADC ticks)\n"
<< " tty+di1_group_avg -> fast stream-only mode: skip CSV/SVG/live outputs and send only averaged tty data\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 average recent noise steps and subtract it\n"
<< " from useful steps before tty output\n"
<< " (only corrected DO1=LOW steps are sent to tty)\n"
<< " noise_avg_steps:N -> number of recent DO1=HIGH noise steps per channel used as subtraction baseline\n"
<< " (required when do1_noise_subtract is enabled)\n"
<< " tty fast stream-only modes -> di1_group_avg or do1_noise_subtract; skip CSV/SVG/live outputs\n"
<< " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n" << " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n"
<< "\n" << "\n"
<< "Profiles:\n" << "Profiles:\n"
@ -531,10 +540,12 @@ void print_help(const char* exe_name) {
<< "This build enables synchronous DIN together with ADC. DI_SYN2 stop edges are detected\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" << "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" << "can either zero ADC samples on change, be exported as a separate synchronous trace, or be ignored.\n"
<< "Outside tty+di1_group_avg fast mode, open live_plot.html in a browser to see the live graph update over time.\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" << "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" << "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" << "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.\n"
<< "Amplitude mode keeps the raw AD8317 voltage as captured on X1; no inversion is applied.\n" << "Amplitude mode keeps the raw AD8317 voltage as captured on X1; no inversion is applied.\n"
<< "\n" << "\n"
<< "Phase profile example:\n" << "Phase profile example:\n"
@ -551,7 +562,13 @@ void print_help(const char* exe_name) {
<< " " << exe_name << " " << exe_name
<< " profile:amplitude clock:internal internal_ref_hz:2000000 start:di_syn2_rise stop:di_syn2_fall" << " 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" << " sample_clock_hz:max range:0.2 di1:trace di1_group_avg duration_ms:100 packet_limit:0"
<< " tty:/tmp/ttyADC_data\n"; << " 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";
} }
Config parse_args(int argc, char** argv) { Config parse_args(int argc, char** argv) {
@ -635,10 +652,18 @@ Config parse_args(int argc, char** argv) {
cfg.di1_group_average = true; cfg.di1_group_average = true;
continue; continue;
} }
if (arg == "do1_noise_subtract") {
cfg.do1_noise_subtract = true;
continue;
}
if (arg == "do1_toggle_per_frame") { if (arg == "do1_toggle_per_frame") {
cfg.do1_toggle_per_frame = true; cfg.do1_toggle_per_frame = true;
continue; continue;
} }
if (starts_with(arg, "noise_avg_steps:")) {
cfg.noise_avg_steps = parse_u32(arg.substr(16), "noise_avg_steps");
continue;
}
if (starts_with(arg, "sample_clock_hz:")) { if (starts_with(arg, "sample_clock_hz:")) {
const std::string value = trim_copy(arg.substr(16)); const std::string value = trim_copy(arg.substr(16));
cfg.sample_clock_specified = true; cfg.sample_clock_specified = true;
@ -750,7 +775,7 @@ Config parse_args(int argc, char** argv) {
cfg.live_update_period_ms = std::max<uint32_t>(cfg.live_update_period_ms, 1000U); cfg.live_update_period_ms = std::max<uint32_t>(cfg.live_update_period_ms, 1000U);
} }
} }
if (cfg.tty_path && cfg.di1_group_average) { if (cfg.tty_path && (cfg.di1_group_average || cfg.do1_noise_subtract)) {
if (!cfg.recv_block_specified) { if (!cfg.recv_block_specified) {
cfg.recv_block_words = std::max<uint32_t>(cfg.recv_block_words, 65536U); cfg.recv_block_words = std::max<uint32_t>(cfg.recv_block_words, 65536U);
} }
@ -773,8 +798,24 @@ Config parse_args(int argc, char** argv) {
if (cfg.di1_group_average && (cfg.di1_mode != Di1Mode::Trace)) { if (cfg.di1_group_average && (cfg.di1_mode != Di1Mode::Trace)) {
fail("di1_group_avg requires di1:trace"); fail("di1_group_avg requires di1:trace");
} }
if (cfg.do1_toggle_per_frame && (cfg.channel_count != 2U)) { if (cfg.do1_noise_subtract) {
fail("do1_toggle_per_frame requires channels:2"); if (!cfg.tty_path.has_value()) {
fail("do1_noise_subtract requires tty:<path>");
}
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.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 (sync_uses_di_syn1(cfg.sync_mode) && sync_uses_di_syn1(cfg.sync_start_mode)) { 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"); fail("clock and start cannot both use DI_SYN1; use start_in or immediate start");
@ -926,6 +967,7 @@ struct Api {
decltype(&X502_GetAdcFreq) GetAdcFreq = nullptr; decltype(&X502_GetAdcFreq) GetAdcFreq = nullptr;
decltype(&X502_SetDinFreq) SetDinFreq = nullptr; decltype(&X502_SetDinFreq) SetDinFreq = nullptr;
decltype(&X502_SetOutFreq) SetOutFreq = nullptr; decltype(&X502_SetOutFreq) SetOutFreq = nullptr;
decltype(&X502_AsyncOutDig) AsyncOutDig = nullptr;
decltype(&X502_SetRefFreq) SetRefFreq = nullptr; decltype(&X502_SetRefFreq) SetRefFreq = nullptr;
decltype(&X502_SetStreamBufSize) SetStreamBufSize = nullptr; decltype(&X502_SetStreamBufSize) SetStreamBufSize = nullptr;
decltype(&X502_SetStreamStep) SetStreamStep = nullptr; decltype(&X502_SetStreamStep) SetStreamStep = nullptr;
@ -936,7 +978,9 @@ struct Api {
decltype(&X502_StreamsStart) StreamsStart = nullptr; decltype(&X502_StreamsStart) StreamsStart = nullptr;
decltype(&X502_GetRecvReadyCount) GetRecvReadyCount = nullptr; decltype(&X502_GetRecvReadyCount) GetRecvReadyCount = nullptr;
decltype(&X502_Recv) Recv = nullptr; decltype(&X502_Recv) Recv = nullptr;
decltype(&X502_PreloadStart) PreloadStart = nullptr; decltype(&X502_OutCycleLoadStart) OutCycleLoadStart = nullptr;
decltype(&X502_OutCycleSetup) OutCycleSetup = nullptr;
decltype(&X502_OutCycleStop) OutCycleStop = nullptr;
decltype(&X502_Send) Send = nullptr; decltype(&X502_Send) Send = nullptr;
decltype(&X502_PrepareData) PrepareData = nullptr; decltype(&X502_PrepareData) PrepareData = nullptr;
decltype(&X502_ProcessData) ProcessData = nullptr; decltype(&X502_ProcessData) ProcessData = nullptr;
@ -983,6 +1027,7 @@ struct Api {
GetAdcFreq = load_symbol<decltype(GetAdcFreq)>(x502_module, "X502_GetAdcFreq"); GetAdcFreq = load_symbol<decltype(GetAdcFreq)>(x502_module, "X502_GetAdcFreq");
SetDinFreq = load_symbol<decltype(SetDinFreq)>(x502_module, "X502_SetDinFreq"); SetDinFreq = load_symbol<decltype(SetDinFreq)>(x502_module, "X502_SetDinFreq");
SetOutFreq = load_symbol<decltype(SetOutFreq)>(x502_module, "X502_SetOutFreq"); SetOutFreq = load_symbol<decltype(SetOutFreq)>(x502_module, "X502_SetOutFreq");
AsyncOutDig = load_symbol<decltype(AsyncOutDig)>(x502_module, "X502_AsyncOutDig");
SetRefFreq = load_symbol<decltype(SetRefFreq)>(x502_module, "X502_SetRefFreq"); SetRefFreq = load_symbol<decltype(SetRefFreq)>(x502_module, "X502_SetRefFreq");
SetStreamBufSize = load_symbol<decltype(SetStreamBufSize)>(x502_module, "X502_SetStreamBufSize"); SetStreamBufSize = load_symbol<decltype(SetStreamBufSize)>(x502_module, "X502_SetStreamBufSize");
SetStreamStep = load_symbol<decltype(SetStreamStep)>(x502_module, "X502_SetStreamStep"); SetStreamStep = load_symbol<decltype(SetStreamStep)>(x502_module, "X502_SetStreamStep");
@ -993,7 +1038,9 @@ struct Api {
StreamsStart = load_symbol<decltype(StreamsStart)>(x502_module, "X502_StreamsStart"); StreamsStart = load_symbol<decltype(StreamsStart)>(x502_module, "X502_StreamsStart");
GetRecvReadyCount = load_symbol<decltype(GetRecvReadyCount)>(x502_module, "X502_GetRecvReadyCount"); GetRecvReadyCount = load_symbol<decltype(GetRecvReadyCount)>(x502_module, "X502_GetRecvReadyCount");
Recv = load_symbol<decltype(Recv)>(x502_module, "X502_Recv"); Recv = load_symbol<decltype(Recv)>(x502_module, "X502_Recv");
PreloadStart = load_symbol<decltype(PreloadStart)>(x502_module, "X502_PreloadStart"); OutCycleLoadStart = try_load_symbol<decltype(OutCycleLoadStart)>(x502_module, "X502_OutCycleLoadStart");
OutCycleSetup = try_load_symbol<decltype(OutCycleSetup)>(x502_module, "X502_OutCycleSetup");
OutCycleStop = try_load_symbol<decltype(OutCycleStop)>(x502_module, "X502_OutCycleStop");
Send = load_symbol<decltype(Send)>(x502_module, "X502_Send"); Send = load_symbol<decltype(Send)>(x502_module, "X502_Send");
PrepareData = load_symbol<decltype(PrepareData)>(x502_module, "X502_PrepareData"); PrepareData = load_symbol<decltype(PrepareData)>(x502_module, "X502_PrepareData");
ProcessData = load_symbol<decltype(ProcessData)>(x502_module, "X502_ProcessData"); ProcessData = load_symbol<decltype(ProcessData)>(x502_module, "X502_ProcessData");
@ -1029,6 +1076,8 @@ constexpr uint32_t kE502DiSyn2Mask =
constexpr uint32_t kE502Digital1Mask = (static_cast<uint32_t>(1U) << 0U); constexpr uint32_t kE502Digital1Mask = (static_cast<uint32_t>(1U) << 0U);
constexpr uint32_t kE502Digital2Mask = (static_cast<uint32_t>(1U) << 1U); constexpr uint32_t kE502Digital2Mask = (static_cast<uint32_t>(1U) << 1U);
constexpr uint32_t kE502Do1Mask = (static_cast<uint32_t>(1U) << 0U); constexpr uint32_t kE502Do1Mask = (static_cast<uint32_t>(1U) << 0U);
constexpr uint32_t kDo1TogglePeriodTicks = 2U;
constexpr uint32_t kDo1CyclePatternWords = kDo1TogglePeriodTicks * 2U;
constexpr uint32_t kStreamInputAdcFlag = 0x80000000U; constexpr uint32_t kStreamInputAdcFlag = 0x80000000U;
constexpr uint32_t kStreamInputCalibratedAdcFlag = 0x40000000U; constexpr uint32_t kStreamInputCalibratedAdcFlag = 0x40000000U;
@ -1182,6 +1231,103 @@ struct TtyGroupAverageState {
} }
}; };
struct Do1NoiseSubtractState {
uint32_t noise_avg_steps = 0;
std::array<std::vector<double>, 2> noise_ring;
std::array<std::size_t, 2> noise_ring_head {};
std::array<std::size_t, 2> noise_ring_size {};
std::array<double, 2> noise_ring_sum {};
std::array<double, 2> step_sum {};
std::array<uint32_t, 2> step_count {};
bool step_level_initialized = false;
bool step_di1_high = false;
uint16_t next_index = 1;
void configure(uint32_t history_steps) {
noise_avg_steps = history_steps;
for (auto& ring : noise_ring) {
ring.assign(noise_avg_steps, 0.0);
}
reset_packet();
}
void clear_step() {
step_sum = {};
step_count = {};
}
void reset_packet() {
clear_step();
step_level_initialized = false;
step_di1_high = false;
next_index = 1;
noise_ring_head = {};
noise_ring_size = {};
noise_ring_sum = {};
}
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<double>(step_count[lch]);
}
void push_noise_average(uint32_t lch, double avg_value) {
if ((noise_avg_steps == 0U) || (lch >= noise_ring.size())) {
return;
}
auto& ring = noise_ring[lch];
auto& head = noise_ring_head[lch];
auto& size = noise_ring_size[lch];
auto& sum = noise_ring_sum[lch];
if (size < noise_avg_steps) {
ring[size] = avg_value;
sum += avg_value;
++size;
return;
}
sum -= ring[head];
ring[head] = avg_value;
sum += avg_value;
head = (head + 1U) % noise_avg_steps;
}
double noise_baseline(uint32_t lch) const {
if (lch >= noise_ring_size.size()) {
return 0.0;
}
const std::size_t size = noise_ring_size[lch];
if (size == 0U) {
return 0.0;
}
return noise_ring_sum[lch] / static_cast<double>(size);
}
void finish_step() { clear_step(); }
};
int16_t pack_raw_code_to_int16(double avg_raw_code) { int16_t pack_raw_code_to_int16(double avg_raw_code) {
const double scaled = const double scaled =
avg_raw_code * 32767.0 / static_cast<double>(X502_ADC_SCALE_CODE_MAX); avg_raw_code * 32767.0 / static_cast<double>(X502_ADC_SCALE_CODE_MAX);
@ -1383,9 +1529,10 @@ int run(const Config& cfg) {
} }
double actual_dout_freq_hz = actual_frame_freq_hz; double actual_dout_freq_hz = actual_frame_freq_hz;
if (cfg.do1_toggle_per_frame) { if (cfg.do1_toggle_per_frame) {
if (actual_frame_freq_hz <= 0.0) { if (actual_sample_clock_hz <= 0.0) {
fail("do1_toggle_per_frame requires a positive ADC frame rate"); 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.SetOutFreq(device.hnd, &actual_dout_freq_hz), "Set DOUT frequency");
} }
@ -1449,7 +1596,8 @@ int run(const Config& cfg) {
<< ((cfg.channel_count >= 2U) ? phy_channel_name(cfg.mode, cfg.ch2) : std::string("disabled")) << "\n" << ((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" << " DI1 handling: " << di1_mode_to_string(cfg.di1_mode) << "\n"
<< " DO1 toggle per frame: " << " DO1 toggle per frame: "
<< (cfg.do1_toggle_per_frame ? std::string("enabled (packet-gated, LOW-first)") : std::string("disabled")) << "\n" << (cfg.do1_toggle_per_frame ? std::string("enabled (hardware cyclic 00110011..., toggle every 2 ADC ticks)")
: std::string("disabled")) << "\n"
<< " DOUT rate: " << " DOUT rate: "
<< (cfg.do1_toggle_per_frame ? std::to_string(actual_dout_freq_hz) + " Hz" : std::string("disabled")) << "\n" << (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" << " ADC range: +/-" << range_to_volts(cfg.range) << " V\n"
@ -1459,9 +1607,14 @@ int run(const Config& cfg) {
std::size_t tty_ring_capacity_bytes = 0; std::size_t tty_ring_capacity_bytes = 0;
std::unique_ptr<TtyProtocolWriter> tty_writer; std::unique_ptr<TtyProtocolWriter> tty_writer;
const bool tty_di1_group_average = cfg.tty_path.has_value() && cfg.di1_group_average; const bool tty_di1_group_average = cfg.tty_path.has_value() && cfg.di1_group_average;
const bool fast_tty_avg_stream_mode = tty_di1_group_average; const bool tty_do1_noise_subtract = cfg.tty_path.has_value() && cfg.do1_noise_subtract;
const bool fast_tty_avg_stream_mode = tty_di1_group_average || tty_do1_noise_subtract;
const uint16_t tty_packet_start_marker = const uint16_t tty_packet_start_marker =
(cfg.profile == CaptureProfile::Amplitude) ? 0x001AU : 0x000AU; (cfg.profile == CaptureProfile::Amplitude) ? 0x001AU : 0x000AU;
const std::string fast_tty_mode_name =
tty_do1_noise_subtract ? std::string("do1_noise_subtract")
: (tty_di1_group_average ? std::string("di1_group_avg")
: std::string("none"));
if (cfg.tty_path) { 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_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; const uint64_t typical_tty_batch_bytes = typical_tty_batch_frames * sizeof(uint16_t) * 4U;
@ -1472,7 +1625,7 @@ int run(const Config& cfg) {
} }
tty_ring_capacity_bytes = static_cast<std::size_t>(tty_ring_bytes_u64); 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); tty_writer = std::make_unique<TtyProtocolWriter>(*cfg.tty_path, tty_ring_capacity_bytes);
if (!tty_di1_group_average) { if (!tty_di1_group_average && !tty_do1_noise_subtract) {
tty_writer->emit_packet_start(tty_packet_start_marker); tty_writer->emit_packet_start(tty_packet_start_marker);
} }
} }
@ -1483,13 +1636,18 @@ int run(const Config& cfg) {
writer->initialize_csv(cfg.channel_count, cfg.di1_mode == Di1Mode::Trace); writer->initialize_csv(cfg.channel_count, cfg.di1_mode == Di1Mode::Trace);
} }
if (fast_tty_avg_stream_mode) { if (fast_tty_avg_stream_mode) {
std::cout << " tty avg stream-only mode: enabled\n" std::cout << " tty fast stream-only mode: enabled (" << fast_tty_mode_name << ")\n"
<< " tty stream output: " << *cfg.tty_path << "\n" << " tty stream output: " << *cfg.tty_path << "\n"
<< " tty ring buffer bytes: " << tty_ring_capacity_bytes << "\n" << " tty ring buffer bytes: " << tty_ring_capacity_bytes << "\n"
<< " recv block words: " << cfg.recv_block_words << "\n" << " recv block words: " << cfg.recv_block_words << "\n"
<< " input step words: " << cfg.input_step_words << "\n" << " input step words: " << cfg.input_step_words << "\n"
<< " input buffer words: " << cfg.input_buffer_words << "\n" << " input buffer words: " << cfg.input_buffer_words << "\n"
<< " tty di1_group_avg: enabled\n" << " tty di1_group_avg: " << (tty_di1_group_average ? "enabled" : "disabled") << "\n"
<< " tty do1_noise_subtract: " << (tty_do1_noise_subtract ? "enabled" : "disabled") << "\n"
<< " do1_noise_subtract marker: "
<< (tty_do1_noise_subtract ? "DI1 from DIN (DO1->DI1 loopback expected)" : "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"; << " stream-only note: ignoring csv/svg/live_html/live_json/live_update_period_ms/svg_history_packets\n";
} else { } else {
std::cout << " live plot html: " << writer->live_html_path() << "\n" std::cout << " live plot html: " << writer->live_html_path() << "\n"
@ -1505,6 +1663,11 @@ int run(const Config& cfg) {
: (cfg.di1_group_average ? std::string("requested, tty disabled") : (cfg.di1_group_average ? std::string("requested, tty disabled")
: std::string("disabled"))) : std::string("disabled")))
<< "\n" << "\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"
<< " SVG history packets kept in RAM: " << " SVG history packets kept in RAM: "
<< ((cfg.svg_history_packets == 0U) ? std::string("all (unbounded)") : std::to_string(cfg.svg_history_packets)) << ((cfg.svg_history_packets == 0U) ? std::string("all (unbounded)") : std::to_string(cfg.svg_history_packets))
<< "\n"; << "\n";
@ -1515,20 +1678,46 @@ int run(const Config& cfg) {
std::cerr << "Warning: Ctrl+C handler could not be installed; continuous mode may stop abruptly.\n"; std::cerr << "Warning: Ctrl+C handler could not be installed; continuous mode may stop abruptly.\n";
} }
std::array<uint32_t, kDo1CyclePatternWords> do1_cycle_pattern {};
std::array<uint32_t, kDo1CyclePatternWords> do1_cycle_encoded {};
if (cfg.do1_toggle_per_frame) { if (cfg.do1_toggle_per_frame) {
expect_ok(api, api.PreloadStart(device.hnd), "Start DOUT preload"); if ((api.OutCycleLoadStart == nullptr) || (api.OutCycleSetup == nullptr) || (api.OutCycleStop == nullptr)) {
const uint32_t preload_low_word = 0U; fail("do1_toggle_per_frame requires X502_OutCycle* API support (cyclic output mode)");
uint32_t preload_encoded_word = 0U; }
for (std::size_t i = 0; i < do1_cycle_pattern.size(); ++i) {
do1_cycle_pattern[i] = (i < kDo1TogglePeriodTicks) ? 0U : kE502Do1Mask;
}
expect_ok(api, expect_ok(api,
api.PrepareData(device.hnd, nullptr, nullptr, &preload_low_word, 1U, 0, &preload_encoded_word), api.OutCycleLoadStart(device.hnd, static_cast<uint32_t>(do1_cycle_pattern.size())),
"Prepare initial DOUT LOW"); "Allocate cyclic DOUT buffer");
const int32_t preload_sent = api.Send(device.hnd, &preload_encoded_word, 1U, cfg.recv_timeout_ms); expect_ok(api,
if (preload_sent < 0) { api.PrepareData(device.hnd,
fail("Send initial DOUT LOW: " + x502_error(api, preload_sent)); nullptr,
} nullptr,
if (preload_sent != 1) { do1_cycle_pattern.data(),
fail("Send initial DOUT LOW did not transmit exactly one word"); static_cast<uint32_t>(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<uint32_t>(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<std::size_t>(sent);
} }
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"); expect_ok(api, api.StreamsStart(device.hnd), "Start streams");
@ -1541,13 +1730,16 @@ int run(const Config& cfg) {
std::deque<double> pending_adc; std::deque<double> pending_adc;
std::deque<double> pending_adc_raw; std::deque<double> pending_adc_raw;
std::deque<uint32_t> pending_din; std::deque<uint32_t> pending_din;
std::deque<uint32_t> pending_dout;
std::deque<CapturePacket> packets; std::deque<CapturePacket> packets;
PacketAccumulator current_packet; PacketAccumulator current_packet;
TtyContinuousState tty_state; TtyContinuousState tty_state;
TtyGroupAverageState tty_group_state; TtyGroupAverageState tty_group_state;
Do1NoiseSubtractState tty_do1_noise_state;
std::vector<uint16_t> tty_frame_words; std::vector<uint16_t> tty_frame_words;
tty_frame_words.reserve(static_cast<std::size_t>(read_capacity_words) * 2U + 16U); tty_frame_words.reserve(static_cast<std::size_t>(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) { if (!fast_tty_avg_stream_mode) {
current_packet.reset(target_frames, cfg.channel_count); current_packet.reset(target_frames, cfg.channel_count);
} }
@ -1572,14 +1764,6 @@ int run(const Config& cfg) {
TickMs stats_window_start = capture_loop_start; TickMs stats_window_start = capture_loop_start;
TickMs last_stats_print = capture_loop_start; TickMs last_stats_print = capture_loop_start;
TickMs last_live_update = 0; TickMs last_live_update = 0;
bool do1_next_state_high = false;
const std::size_t max_pending_dout_words = 1000000U;
std::vector<uint32_t> dout_prepare_input;
std::vector<uint32_t> 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_raw_words = 0;
uint64_t total_adc_samples = 0; uint64_t total_adc_samples = 0;
@ -1622,11 +1806,13 @@ int run(const Config& cfg) {
<< ", DIN samples/s=" << din_samples_per_s << ", DIN samples/s=" << din_samples_per_s
<< ", frames/s per channel=" << frames_per_ch_per_s << ", frames/s per channel=" << frames_per_ch_per_s
<< ", packets/s=" << packets_per_s; << ", packets/s=" << packets_per_s;
if (cfg.di1_mode == Di1Mode::ZeroOnChange) { if (!fast_tty_avg_stream_mode) {
std::cout << ", zeroed on DI1 change=" << zeroed_fraction << "% (" if (cfg.di1_mode == Di1Mode::ZeroOnChange) {
<< stats_zeroed_samples << "/" << stats_stored_adc_samples << ")"; std::cout << ", zeroed on DI1 change=" << zeroed_fraction << "% ("
} else if (cfg.di1_mode == Di1Mode::Trace) { << stats_zeroed_samples << "/" << stats_stored_adc_samples << ")";
std::cout << ", DI1 trace=enabled"; } else if (cfg.di1_mode == Di1Mode::Trace) {
std::cout << ", DI1 trace=enabled";
}
} }
if (tty_writer) { if (tty_writer) {
std::cout << ", tty_frames_written=" << tty_frames_written std::cout << ", tty_frames_written=" << tty_frames_written
@ -1681,6 +1867,49 @@ int run(const Config& cfg) {
++packet_avg_steps; ++packet_avg_steps;
}; };
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)) {
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.push_noise_average(0U, ch1_avg);
if (cfg.channel_count > 1U) {
tty_do1_noise_state.push_noise_average(1U, ch2_avg);
}
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.noise_baseline(0U);
const double ch2_corrected =
(cfg.channel_count <= 1U) ? 0.0 : (ch2_avg - tty_do1_noise_state.noise_baseline(1U));
append_tty_frame((cfg.profile == CaptureProfile::Amplitude) ? 0x001AU : 0x000AU,
tty_do1_noise_state.next_index,
static_cast<uint16_t>(pack_raw_code_to_int16(ch1_corrected)),
static_cast<uint16_t>(pack_raw_code_to_int16(ch2_corrected)));
++tty_do1_noise_state.next_index;
++packet_avg_steps;
tty_do1_noise_state.finish_step();
};
auto flush_tty_frames = [&]() { auto flush_tty_frames = [&]() {
if (!tty_writer || tty_frame_words.empty()) { if (!tty_writer || tty_frame_words.empty()) {
return; return;
@ -1696,66 +1925,6 @@ 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<std::size_t>(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<uint32_t>(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<uint32_t>(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<std::size_t>(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 = [&]() { auto start_packet = [&]() {
if (packet_active) { if (packet_active) {
return; return;
@ -1771,9 +1940,9 @@ int run(const Config& cfg) {
if (tty_di1_group_average) { if (tty_di1_group_average) {
tty_group_state.reset_packet(); tty_group_state.reset_packet();
append_tty_packet_start(); append_tty_packet_start();
} } else if (tty_do1_noise_subtract) {
if (cfg.do1_toggle_per_frame) { tty_do1_noise_state.reset_packet();
do1_next_state_high = false; append_tty_packet_start();
} }
packet_active = true; packet_active = true;
}; };
@ -1790,6 +1959,10 @@ int run(const Config& cfg) {
if (tty_di1_group_average) { if (tty_di1_group_average) {
append_tty_group_step(); append_tty_group_step();
tty_group_state.clear_step(); tty_group_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();
tty_do1_noise_state.clear_step();
} }
const std::size_t frames = fast_tty_avg_stream_mode const std::size_t frames = fast_tty_avg_stream_mode
@ -1908,6 +2081,8 @@ int run(const Config& cfg) {
} }
if (tty_di1_group_average) { if (tty_di1_group_average) {
tty_group_state.clear_step(); tty_group_state.clear_step();
} else if (tty_do1_noise_subtract) {
tty_do1_noise_state.clear_step();
} }
}; };
@ -2132,7 +2307,6 @@ int run(const Config& cfg) {
} }
trigger_prev_level = di_syn2_level; trigger_prev_level = di_syn2_level;
} }
if (!packet_active && (cfg.sync_start_mode == X502_SYNC_INTERNAL)) { if (!packet_active && (cfg.sync_start_mode == X502_SYNC_INTERNAL)) {
start_packet(); start_packet();
} }
@ -2150,24 +2324,12 @@ int run(const Config& cfg) {
if (packet_active && stop_edge) { if (packet_active && stop_edge) {
finish_packet(PacketCloseReason::ExternalStopEdge); 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)) { if ((cfg.packet_limit != 0U) && (total_completed_packets >= cfg.packet_limit)) {
stop_loop_requested = true; stop_loop_requested = true;
} }
continue; 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) { if (!packet_active) {
continue; continue;
} }
@ -2182,6 +2344,13 @@ int run(const Config& cfg) {
if (tty_di1_group_average && di1_changed) { if (tty_di1_group_average && di1_changed) {
append_tty_group_step(); append_tty_group_step();
tty_group_state.clear_step(); tty_group_state.clear_step();
} 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();
tty_do1_noise_state.start_new_step(di1_level);
}
} }
if (!fast_tty_avg_stream_mode && if (!fast_tty_avg_stream_mode &&
@ -2193,7 +2362,11 @@ int run(const Config& cfg) {
if (fast_tty_avg_stream_mode) { if (fast_tty_avg_stream_mode) {
if (fast_packet_frames < target_frames) { if (fast_packet_frames < target_frames) {
tty_group_state.add_sample(lch, adc_raw_value); if (tty_do1_noise_subtract) {
tty_do1_noise_state.add_sample(lch, adc_raw_value);
} else {
tty_group_state.add_sample(lch, adc_raw_value);
}
if (lch == (cfg.channel_count - 1U)) { if (lch == (cfg.channel_count - 1U)) {
++fast_packet_frames; ++fast_packet_frames;
++total_completed_frames; ++total_completed_frames;
@ -2238,7 +2411,6 @@ int run(const Config& cfg) {
} }
} }
flush_dout_queue(0U, false);
flush_tty_frames(); flush_tty_frames();
if (console_stop_requested()) { if (console_stop_requested()) {
@ -2254,11 +2426,15 @@ int run(const Config& cfg) {
} }
} }
append_dout_word(false); if (cfg.do1_toggle_per_frame) {
flush_dout_queue(cfg.recv_timeout_ms, true); 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"); expect_ok(api, api.StreamsStop(device.hnd), "Stop streams");
device.streams_started = false; device.streams_started = false;
if (cfg.do1_toggle_per_frame) {
expect_ok(api, api.AsyncOutDig(device.hnd, 0U, ~kE502Do1Mask), "Force DO1 LOW after cyclic DOUT stop");
}
if ((cfg.stats_period_ms != 0U) && ((stats_raw_words != 0U) || (stats_adc_samples != 0U) || 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_din_samples != 0U) || (stats_stored_adc_samples != 0U) || (stats_zeroed_samples != 0U) ||
@ -2274,26 +2450,15 @@ int run(const Config& cfg) {
} }
if (!fast_tty_avg_stream_mode) { if (!fast_tty_avg_stream_mode) {
const std::vector<CapturePacket> svg_packets(packets.begin(), packets.end()); std::cout << "Captured " << total_completed_packets << " packet(s) in capture mode\n";
writer->write(svg_packets, actual_frame_freq_hz, range_to_volts(cfg.range));
std::size_t total_packet_frames = 0;
for (const auto& packet : svg_packets) {
total_packet_frames += (packet.channel_count <= 1U)
? packet.ch1.size()
: std::min(packet.ch1.size(), packet.ch2.size());
}
std::cout << "Captured " << total_completed_packets << " packet(s), "
<< total_packet_frames << " total frames per channel kept for final SVG\n";
if (cfg.di1_mode == Di1Mode::ZeroOnChange) { if (cfg.di1_mode == Di1Mode::ZeroOnChange) {
std::cout << "ADC samples forced to 0 on DI1 change: " << total_zeroed_samples << "\n"; std::cout << "ADC samples forced to 0 on DI1 change: " << total_zeroed_samples << "\n";
} else if (cfg.di1_mode == Di1Mode::Trace) { } else if (cfg.di1_mode == Di1Mode::Trace) {
std::cout << "DI1 synchronous trace exported to CSV/live plot/SVG\n"; std::cout << "DI1 synchronous trace exported to live plot\n";
} }
} else { } else {
std::cout << "Captured " << total_completed_packets std::cout << "Captured " << total_completed_packets
<< " packet(s) in tty avg stream-only mode\n"; << " packet(s) in tty fast stream-only mode (" << fast_tty_mode_name << ")\n";
} }
std::cout << "Average stats: " std::cout << "Average stats: "
<< "MB/s=" << std::fixed << std::setprecision(3) << "MB/s=" << std::fixed << std::setprecision(3)
@ -2313,14 +2478,16 @@ int run(const Config& cfg) {
<< (static_cast<double>(total_completed_packets) / << (static_cast<double>(total_completed_packets) /
std::max(1e-9, static_cast<double>(tick_count_ms() - capture_loop_start) / 1000.0)) std::max(1e-9, static_cast<double>(tick_count_ms() - capture_loop_start) / 1000.0))
<< ", packets captured=" << total_completed_packets; << ", packets captured=" << total_completed_packets;
if (cfg.di1_mode == Di1Mode::ZeroOnChange) { if (!fast_tty_avg_stream_mode) {
std::cout << ", zeroed on DI1 change=" if (cfg.di1_mode == Di1Mode::ZeroOnChange) {
<< ((total_stored_adc_samples == 0U) std::cout << ", zeroed on DI1 change="
? 0.0 << ((total_stored_adc_samples == 0U)
: (100.0 * static_cast<double>(total_zeroed_samples) / static_cast<double>(total_stored_adc_samples))) ? 0.0
<< "% (" << total_zeroed_samples << "/" << total_stored_adc_samples << ")"; : (100.0 * static_cast<double>(total_zeroed_samples) / static_cast<double>(total_stored_adc_samples)))
} else if (cfg.di1_mode == Di1Mode::Trace) { << "% (" << total_zeroed_samples << "/" << total_stored_adc_samples << ")";
std::cout << ", DI1 trace=enabled"; } else if (cfg.di1_mode == Di1Mode::Trace) {
std::cout << ", DI1 trace=enabled";
}
} }
if (tty_writer) { if (tty_writer) {
std::cout << ", tty_frames_written=" << tty_final_stats.frames_written std::cout << ", tty_frames_written=" << tty_final_stats.frames_written
@ -2328,14 +2495,9 @@ int run(const Config& cfg) {
<< ", tty_ring_overflows=" << tty_final_stats.ring_overflows; << ", tty_ring_overflows=" << tty_final_stats.ring_overflows;
} }
if (fast_tty_avg_stream_mode) { if (fast_tty_avg_stream_mode) {
std::cout << ", tty avg stream-only mode=enabled"; std::cout << ", tty fast stream-only mode=" << fast_tty_mode_name;
} }
std::cout << "\n"; std::cout << "\n";
if (!fast_tty_avg_stream_mode) {
std::cout << "Final SVG packets retained in memory: " << packets.size() << "\n"
<< "CSV: " << cfg.csv_path << "\n"
<< "SVG: " << cfg.svg_path << "\n";
}
return 0; return 0;
} }

45
run_do1_noise_subtract.sh Executable file
View File

@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
BIN="${BIN:-./main.exe}"
TTY_PATH="${TTY_PATH:-/tmp/ttyADC_data}"
LIB_DIR="${LIB_DIR:-$HOME/.local/lib}"
NOISE_AVG_STEPS="${NOISE_AVG_STEPS:-}"
if [[ ! -x "$BIN" ]]; then
echo "Binary '$BIN' not found or not executable. Run ./build_main.sh first." >&2
exit 1
fi
if [[ -z "$NOISE_AVG_STEPS" ]]; then
echo "Set NOISE_AVG_STEPS to a positive integer, for example: NOISE_AVG_STEPS=16" >&2
exit 1
fi
if ! [[ "$NOISE_AVG_STEPS" =~ ^[0-9]+$ ]] || [[ "$NOISE_AVG_STEPS" -eq 0 ]]; then
echo "NOISE_AVG_STEPS must be a positive integer, got '$NOISE_AVG_STEPS'" >&2
exit 1
fi
if [[ -d "$LIB_DIR" ]]; then
export LD_LIBRARY_PATH="${LIB_DIR}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
fi
exec "$BIN" \
clock:internal \
internal_ref_hz:2000000 \
start:di_syn2_rise \
stop:di_syn2_fall \
sample_clock_hz:max \
range:5 \
duration_ms:100 \
packet_limit:0 \
do1_toggle_per_frame \
do1_noise_subtract \
"noise_avg_steps:${NOISE_AVG_STEPS}" \
profile:phase \
"tty:${TTY_PATH}" \
"$@"