|
|
|
|
@ -113,6 +113,11 @@ struct Config {
|
|
|
|
|
std::string live_json_path = "live_plot.json";
|
|
|
|
|
std::optional<std::string> 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<uint32_t> noise_avg_steps;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
[[noreturn]] void fail(const std::string& message) {
|
|
|
|
|
@ -470,6 +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] [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"
|
|
|
|
|
@ -494,17 +500,34 @@ void print_help(const char* exe_name) {
|
|
|
|
|
<< " 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+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)"
|
|
|
|
|
<< " (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"
|
|
|
|
|
<< " 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+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"
|
|
|
|
|
<< " di1_group_avg -> with tty + di1:trace, emit one averaged 4-word step per constant DI1 run\n"
|
|
|
|
|
<< " tty+di1_group_avg -> fast stream-only mode: skip CSV/SVG/live outputs and send only averaged tty data\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"
|
|
|
|
|
<< " 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"
|
|
|
|
|
@ -527,10 +550,16 @@ void print_help(const char* exe_name) {
|
|
|
|
|
<< "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+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"
|
|
|
|
|
<< "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"
|
|
|
|
|
@ -547,7 +576,19 @@ void print_help(const char* exe_name) {
|
|
|
|
|
<< " " << 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";
|
|
|
|
|
<< " 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) {
|
|
|
|
|
@ -631,6 +672,26 @@ Config parse_args(int argc, char** argv) {
|
|
|
|
|
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 (starts_with(arg, "sample_clock_hz:")) {
|
|
|
|
|
const std::string value = trim_copy(arg.substr(16));
|
|
|
|
|
cfg.sample_clock_specified = true;
|
|
|
|
|
@ -742,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) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
@ -765,6 +826,74 @@ 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.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>");
|
|
|
|
|
}
|
|
|
|
|
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:<path>");
|
|
|
|
|
}
|
|
|
|
|
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:<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");
|
|
|
|
|
}
|
|
|
|
|
@ -914,6 +1043,8 @@ struct Api {
|
|
|
|
|
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_SetRefFreq) SetRefFreq = nullptr;
|
|
|
|
|
decltype(&X502_SetStreamBufSize) SetStreamBufSize = nullptr;
|
|
|
|
|
decltype(&X502_SetStreamStep) SetStreamStep = nullptr;
|
|
|
|
|
@ -924,6 +1055,11 @@ struct Api {
|
|
|
|
|
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;
|
|
|
|
|
@ -967,6 +1103,8 @@ struct Api {
|
|
|
|
|
SetAdcFreq = load_symbol<decltype(SetAdcFreq)>(x502_module, "X502_SetAdcFreq");
|
|
|
|
|
GetAdcFreq = load_symbol<decltype(GetAdcFreq)>(x502_module, "X502_GetAdcFreq");
|
|
|
|
|
SetDinFreq = load_symbol<decltype(SetDinFreq)>(x502_module, "X502_SetDinFreq");
|
|
|
|
|
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");
|
|
|
|
|
SetStreamBufSize = load_symbol<decltype(SetStreamBufSize)>(x502_module, "X502_SetStreamBufSize");
|
|
|
|
|
SetStreamStep = load_symbol<decltype(SetStreamStep)>(x502_module, "X502_SetStreamStep");
|
|
|
|
|
@ -977,6 +1115,11 @@ struct Api {
|
|
|
|
|
StreamsStart = load_symbol<decltype(StreamsStart)>(x502_module, "X502_StreamsStart");
|
|
|
|
|
GetRecvReadyCount = load_symbol<decltype(GetRecvReadyCount)>(x502_module, "X502_GetRecvReadyCount");
|
|
|
|
|
Recv = load_symbol<decltype(Recv)>(x502_module, "X502_Recv");
|
|
|
|
|
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");
|
|
|
|
|
PrepareData = load_symbol<decltype(PrepareData)>(x502_module, "X502_PrepareData");
|
|
|
|
|
ProcessData = load_symbol<decltype(ProcessData)>(x502_module, "X502_ProcessData");
|
|
|
|
|
|
|
|
|
|
OpenUsb = load_symbol<decltype(OpenUsb)>(e502_module, "E502_OpenUsb");
|
|
|
|
|
@ -1009,6 +1152,9 @@ constexpr uint32_t kE502DiSyn2Mask =
|
|
|
|
|
(static_cast<uint32_t>(1U) << 13U) | (static_cast<uint32_t>(1U) << 17U);
|
|
|
|
|
constexpr uint32_t kE502Digital1Mask = (static_cast<uint32_t>(1U) << 0U);
|
|
|
|
|
constexpr uint32_t kE502Digital2Mask = (static_cast<uint32_t>(1U) << 1U);
|
|
|
|
|
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 kStreamInputCalibratedAdcFlag = 0x40000000U;
|
|
|
|
|
|
|
|
|
|
@ -1162,6 +1308,244 @@ struct TtyGroupAverageState {
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Do1RawTaggedState {
|
|
|
|
|
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 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<double>(step_count[lch]);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Do1NoiseSubtractState {
|
|
|
|
|
std::array<double, 2> step_sum {};
|
|
|
|
|
std::array<uint32_t, 2> step_count {};
|
|
|
|
|
std::array<double, 2> last_high_avg {};
|
|
|
|
|
std::array<bool, 2> 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<double>(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<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);
|
|
|
|
|
@ -1361,9 +1745,21 @@ 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_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) {
|
|
|
|
|
@ -1381,7 +1777,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<std::size_t>(
|
|
|
|
|
1, static_cast<std::size_t>(std::llround((cfg.duration_ms / 1000.0) * actual_frame_freq_hz)));
|
|
|
|
|
@ -1413,6 +1813,11 @@ 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 (hardware cyclic 00110011..., toggle every 2 ADC ticks)")
|
|
|
|
|
: 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";
|
|
|
|
|
|
|
|
|
|
@ -1420,9 +1825,19 @@ int run(const Config& cfg) {
|
|
|
|
|
std::size_t tty_ring_capacity_bytes = 0;
|
|
|
|
|
std::unique_ptr<TtyProtocolWriter> tty_writer;
|
|
|
|
|
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 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<uint64_t>(read_capacity_words) + 1U) / 2U;
|
|
|
|
|
const uint64_t typical_tty_batch_bytes = typical_tty_batch_frames * sizeof(uint16_t) * 4U;
|
|
|
|
|
@ -1433,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) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -1444,13 +1860,24 @@ int run(const Config& cfg) {
|
|
|
|
|
writer->initialize_csv(cfg.channel_count, cfg.di1_mode == Di1Mode::Trace);
|
|
|
|
|
}
|
|
|
|
|
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 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: enabled\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"
|
|
|
|
|
@ -1466,6 +1893,21 @@ int run(const Config& cfg) {
|
|
|
|
|
: (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";
|
|
|
|
|
@ -1476,6 +1918,48 @@ int run(const Config& cfg) {
|
|
|
|
|
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 ((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) {
|
|
|
|
|
do1_cycle_pattern[i] = (i < kDo1TogglePeriodTicks) ? 0U : kE502Do1Mask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect_ok(api,
|
|
|
|
|
api.OutCycleLoadStart(device.hnd, static_cast<uint32_t>(do1_cycle_pattern.size())),
|
|
|
|
|
"Allocate cyclic DOUT buffer");
|
|
|
|
|
expect_ok(api,
|
|
|
|
|
api.PrepareData(device.hnd,
|
|
|
|
|
nullptr,
|
|
|
|
|
nullptr,
|
|
|
|
|
do1_cycle_pattern.data(),
|
|
|
|
|
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");
|
|
|
|
|
device.streams_started = true;
|
|
|
|
|
|
|
|
|
|
@ -1490,8 +1974,14 @@ int run(const Config& cfg) {
|
|
|
|
|
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;
|
|
|
|
|
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) {
|
|
|
|
|
tty_do1_noise_state.configure(*cfg.noise_avg_steps);
|
|
|
|
|
}
|
|
|
|
|
if (!fast_tty_avg_stream_mode) {
|
|
|
|
|
current_packet.reset(target_frames, cfg.channel_count);
|
|
|
|
|
}
|
|
|
|
|
@ -1558,12 +2048,14 @@ int run(const Config& cfg) {
|
|
|
|
|
<< ", 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
|
|
|
|
|
@ -1617,6 +2109,115 @@ int run(const Config& cfg) {
|
|
|
|
|
++packet_avg_steps;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
append_tty_frame(marker,
|
|
|
|
|
tty_do1_raw_state.next_index,
|
|
|
|
|
static_cast<uint16_t>(pack_raw_code_to_int16(ch1_avg)),
|
|
|
|
|
static_cast<uint16_t>(pack_raw_code_to_int16(ch2_avg)));
|
|
|
|
|
++tty_do1_raw_state.next_index;
|
|
|
|
|
++packet_avg_steps;
|
|
|
|
|
tty_do1_raw_state.clear_step();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
|
|
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.consume_pending_high();
|
|
|
|
|
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;
|
|
|
|
|
@ -1647,6 +2248,15 @@ int run(const Config& cfg) {
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
packet_active = true;
|
|
|
|
|
};
|
|
|
|
|
@ -1663,6 +2273,16 @@ int run(const Config& cfg) {
|
|
|
|
|
if (tty_di1_group_average) {
|
|
|
|
|
append_tty_group_step();
|
|
|
|
|
tty_group_state.clear_step();
|
|
|
|
|
} else if (tty_do1_raw_tty_marked) {
|
|
|
|
|
append_tty_do1_raw_marked_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();
|
|
|
|
|
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
|
|
|
|
|
@ -1781,6 +2401,12 @@ int run(const Config& cfg) {
|
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@ -2005,7 +2631,6 @@ int run(const Config& cfg) {
|
|
|
|
|
}
|
|
|
|
|
trigger_prev_level = di_syn2_level;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!packet_active && (cfg.sync_start_mode == X502_SYNC_INTERNAL)) {
|
|
|
|
|
start_packet();
|
|
|
|
|
}
|
|
|
|
|
@ -2033,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;
|
|
|
|
|
@ -2043,6 +2669,20 @@ int run(const Config& cfg) {
|
|
|
|
|
if (tty_di1_group_average && di1_changed) {
|
|
|
|
|
append_tty_group_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();
|
|
|
|
|
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();
|
|
|
|
|
tty_do1_noise_state.start_new_step(di1_level);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!fast_tty_avg_stream_mode &&
|
|
|
|
|
@ -2054,7 +2694,40 @@ int run(const Config& cfg) {
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
} 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);
|
|
|
|
|
} else {
|
|
|
|
|
tty_group_state.add_sample(lch, adc_raw_value);
|
|
|
|
|
}
|
|
|
|
|
if (lch == (cfg.channel_count - 1U)) {
|
|
|
|
|
++fast_packet_frames;
|
|
|
|
|
++total_completed_frames;
|
|
|
|
|
@ -2114,8 +2787,15 @@ int run(const Config& cfg) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
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) ||
|
|
|
|
|
(stats_din_samples != 0U) || (stats_stored_adc_samples != 0U) || (stats_zeroed_samples != 0U) ||
|
|
|
|
|
@ -2131,26 +2811,15 @@ int run(const Config& cfg) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!fast_tty_avg_stream_mode) {
|
|
|
|
|
const std::vector<CapturePacket> svg_packets(packets.begin(), packets.end());
|
|
|
|
|
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";
|
|
|
|
|
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 CSV/live plot/SVG\n";
|
|
|
|
|
std::cout << "DI1 synchronous trace exported to live plot\n";
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
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: "
|
|
|
|
|
<< "MB/s=" << std::fixed << std::setprecision(3)
|
|
|
|
|
@ -2170,6 +2839,7 @@ int run(const Config& cfg) {
|
|
|
|
|
<< (static_cast<double>(total_completed_packets) /
|
|
|
|
|
std::max(1e-9, static_cast<double>(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)
|
|
|
|
|
@ -2179,20 +2849,16 @@ int run(const Config& cfg) {
|
|
|
|
|
} 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 avg stream-only mode=enabled";
|
|
|
|
|
std::cout << ", tty fast stream-only mode=" << fast_tty_mode_name;
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|