Compare commits

...

2 Commits

Author SHA1 Message Date
awe
71bf90f003 upd start 2026-04-21 17:03:30 +03:00
awe
f462e65cc2 main - Update main.cpp 2026-04-21 17:02:46 +03:00
2 changed files with 145 additions and 1 deletions

145
main.cpp
View File

@ -113,6 +113,7 @@ 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;
};
[[noreturn]] void fail(const std::string& message) {
@ -470,6 +471,7 @@ void print_help(const char* exe_name) {
<< " [di1:zero|trace|ignore]\n"
<< " [duration_ms:100] [packet_limit:0] [csv:capture.csv] [svg:capture.svg]\n"
<< " [live_html:live_plot.html] [live_json:live_plot.json] [tty:/tmp/ttyADC_data] [di1_group_avg]\n"
<< " [do1_toggle_per_frame]\n"
<< " [recv_block:32768] [stats_period_ms:1000] [live_update_period_ms:1000] [svg_history_packets:50] [start_wait_ms:10000]\n"
<< " [buffer_words:8388608] [step_words:32768]\n"
<< " [pullup_syn1] [pullup_syn2] [pulldown_conv_in] [pulldown_start_in]\n"
@ -504,6 +506,8 @@ void print_help(const char* exe_name) {
<< " live_html/live_json -> live graph files updated as packets arrive outside tty+di1_group_avg fast mode\n"
<< " tty:/tmp/ttyADC_data -> write a continuous legacy 4-word CH1/CH2 stream; with channels:1, CH2 is 0\n"
<< " di1_group_avg -> with tty + di1:trace, emit one averaged 4-word step per constant DI1 run\n"
<< " do1_toggle_per_frame -> packet-gated DO1 output: LOW,HIGH,LOW,HIGH per 2-channel frame inside packet\n"
<< " outside packet windows DO1 is forced LOW\n"
<< " tty+di1_group_avg -> fast stream-only mode: skip CSV/SVG/live outputs and send only averaged tty data\n"
<< " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n"
<< "\n"
@ -631,6 +635,10 @@ Config parse_args(int argc, char** argv) {
cfg.di1_group_average = true;
continue;
}
if (arg == "do1_toggle_per_frame") {
cfg.do1_toggle_per_frame = true;
continue;
}
if (starts_with(arg, "sample_clock_hz:")) {
const std::string value = trim_copy(arg.substr(16));
cfg.sample_clock_specified = true;
@ -765,6 +773,9 @@ Config parse_args(int argc, char** argv) {
if (cfg.di1_group_average && (cfg.di1_mode != Di1Mode::Trace)) {
fail("di1_group_avg requires di1:trace");
}
if (cfg.do1_toggle_per_frame && (cfg.channel_count != 2U)) {
fail("do1_toggle_per_frame requires channels:2");
}
if (sync_uses_di_syn1(cfg.sync_mode) && sync_uses_di_syn1(cfg.sync_start_mode)) {
fail("clock and start cannot both use DI_SYN1; use start_in or immediate start");
}
@ -914,6 +925,7 @@ struct Api {
decltype(&X502_SetAdcFreq) SetAdcFreq = nullptr;
decltype(&X502_GetAdcFreq) GetAdcFreq = nullptr;
decltype(&X502_SetDinFreq) SetDinFreq = nullptr;
decltype(&X502_SetOutFreq) SetOutFreq = nullptr;
decltype(&X502_SetRefFreq) SetRefFreq = nullptr;
decltype(&X502_SetStreamBufSize) SetStreamBufSize = nullptr;
decltype(&X502_SetStreamStep) SetStreamStep = nullptr;
@ -924,6 +936,9 @@ struct Api {
decltype(&X502_StreamsStart) StreamsStart = nullptr;
decltype(&X502_GetRecvReadyCount) GetRecvReadyCount = nullptr;
decltype(&X502_Recv) Recv = nullptr;
decltype(&X502_PreloadStart) PreloadStart = nullptr;
decltype(&X502_Send) Send = nullptr;
decltype(&X502_PrepareData) PrepareData = nullptr;
decltype(&X502_ProcessData) ProcessData = nullptr;
decltype(&E502_OpenUsb) OpenUsb = nullptr;
@ -967,6 +982,7 @@ struct Api {
SetAdcFreq = load_symbol<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");
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 +993,9 @@ 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");
PreloadStart = load_symbol<decltype(PreloadStart)>(x502_module, "X502_PreloadStart");
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 +1028,7 @@ 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 kStreamInputAdcFlag = 0x80000000U;
constexpr uint32_t kStreamInputCalibratedAdcFlag = 0x40000000U;
@ -1361,9 +1381,20 @@ int run(const Config& cfg) {
<< " words/s, while the documented Ethernet input-only limit is about 2500000 words/s.";
fail(message.str());
}
double actual_dout_freq_hz = actual_frame_freq_hz;
if (cfg.do1_toggle_per_frame) {
if (actual_frame_freq_hz <= 0.0) {
fail("do1_toggle_per_frame requires a positive ADC frame rate");
}
expect_ok(api, api.SetOutFreq(device.hnd, &actual_dout_freq_hz), "Set DOUT frequency");
}
expect_ok(api, api.SetStreamBufSize(device.hnd, X502_STREAM_CH_IN, cfg.input_buffer_words), "Set input buffer size");
expect_ok(api, api.SetStreamStep(device.hnd, X502_STREAM_CH_IN, cfg.input_step_words), "Set input stream step");
if (cfg.do1_toggle_per_frame) {
expect_ok(api, api.SetStreamBufSize(device.hnd, X502_STREAM_CH_OUT, cfg.input_buffer_words), "Set output buffer size");
expect_ok(api, api.SetStreamStep(device.hnd, X502_STREAM_CH_OUT, cfg.input_step_words), "Set output stream step");
}
uint32_t pullups = 0;
if (cfg.pullup_syn1) {
@ -1381,7 +1412,11 @@ int run(const Config& cfg) {
expect_ok(api, api.SetDigInPullup(device.hnd, pullups), "Set digital input pullups/pulldowns");
expect_ok(api, api.Configure(device.hnd, 0), "Configure device");
expect_ok(api, api.StreamsEnable(device.hnd, X502_STREAM_ADC | X502_STREAM_DIN), "Enable ADC+DIN stream");
uint32_t enabled_streams = X502_STREAM_ADC | X502_STREAM_DIN;
if (cfg.do1_toggle_per_frame) {
enabled_streams |= X502_STREAM_DOUT;
}
expect_ok(api, api.StreamsEnable(device.hnd, enabled_streams), "Enable data streams");
const std::size_t target_frames = std::max<std::size_t>(
1, static_cast<std::size_t>(std::llround((cfg.duration_ms / 1000.0) * actual_frame_freq_hz)));
@ -1413,6 +1448,10 @@ int run(const Config& cfg) {
<< " channel 2: "
<< ((cfg.channel_count >= 2U) ? phy_channel_name(cfg.mode, cfg.ch2) : std::string("disabled")) << "\n"
<< " DI1 handling: " << di1_mode_to_string(cfg.di1_mode) << "\n"
<< " DO1 toggle per frame: "
<< (cfg.do1_toggle_per_frame ? std::string("enabled (packet-gated, LOW-first)") : std::string("disabled")) << "\n"
<< " DOUT rate: "
<< (cfg.do1_toggle_per_frame ? std::to_string(actual_dout_freq_hz) + " Hz" : std::string("disabled")) << "\n"
<< " ADC range: +/-" << range_to_volts(cfg.range) << " V\n"
<< " max frames per packet per channel: " << target_frames << "\n";
@ -1476,6 +1515,22 @@ int run(const Config& cfg) {
std::cerr << "Warning: Ctrl+C handler could not be installed; continuous mode may stop abruptly.\n";
}
if (cfg.do1_toggle_per_frame) {
expect_ok(api, api.PreloadStart(device.hnd), "Start DOUT preload");
const uint32_t preload_low_word = 0U;
uint32_t preload_encoded_word = 0U;
expect_ok(api,
api.PrepareData(device.hnd, nullptr, nullptr, &preload_low_word, 1U, 0, &preload_encoded_word),
"Prepare initial DOUT LOW");
const int32_t preload_sent = api.Send(device.hnd, &preload_encoded_word, 1U, cfg.recv_timeout_ms);
if (preload_sent < 0) {
fail("Send initial DOUT LOW: " + x502_error(api, preload_sent));
}
if (preload_sent != 1) {
fail("Send initial DOUT LOW did not transmit exactly one word");
}
}
expect_ok(api, api.StreamsStart(device.hnd), "Start streams");
device.streams_started = true;
@ -1486,6 +1541,7 @@ int run(const Config& cfg) {
std::deque<double> pending_adc;
std::deque<double> pending_adc_raw;
std::deque<uint32_t> pending_din;
std::deque<uint32_t> pending_dout;
std::deque<CapturePacket> packets;
PacketAccumulator current_packet;
TtyContinuousState tty_state;
@ -1516,6 +1572,14 @@ int run(const Config& cfg) {
TickMs stats_window_start = capture_loop_start;
TickMs last_stats_print = capture_loop_start;
TickMs last_live_update = 0;
bool do1_next_state_high = false;
const std::size_t max_pending_dout_words = 1000000U;
std::vector<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_adc_samples = 0;
@ -1632,6 +1696,66 @@ int run(const Config& cfg) {
}
};
auto append_dout_word = [&](bool do1_high) {
if (!cfg.do1_toggle_per_frame) {
return;
}
pending_dout.push_back(do1_high ? kE502Do1Mask : 0U);
if (pending_dout.size() > max_pending_dout_words) {
fail("Internal DOUT backlog grew too large");
}
};
auto flush_dout_queue = [&](uint32_t send_timeout_ms, bool require_full_flush) {
if (!cfg.do1_toggle_per_frame || pending_dout.empty()) {
return;
}
while (!pending_dout.empty()) {
const std::size_t chunk_words = std::min<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 = [&]() {
if (packet_active) {
return;
@ -1648,6 +1772,9 @@ int run(const Config& cfg) {
tty_group_state.reset_packet();
append_tty_packet_start();
}
if (cfg.do1_toggle_per_frame) {
do1_next_state_high = false;
}
packet_active = true;
};
@ -2023,12 +2150,24 @@ int run(const Config& cfg) {
if (packet_active && stop_edge) {
finish_packet(PacketCloseReason::ExternalStopEdge);
if (cfg.do1_toggle_per_frame && (lch == (cfg.channel_count - 1U))) {
append_dout_word(false);
}
if ((cfg.packet_limit != 0U) && (total_completed_packets >= cfg.packet_limit)) {
stop_loop_requested = true;
}
continue;
}
if (cfg.do1_toggle_per_frame && (lch == (cfg.channel_count - 1U))) {
if (packet_active) {
append_dout_word(do1_next_state_high);
do1_next_state_high = !do1_next_state_high;
} else {
append_dout_word(false);
}
}
if (!packet_active) {
continue;
}
@ -2099,6 +2238,7 @@ int run(const Config& cfg) {
}
}
flush_dout_queue(0U, false);
flush_tty_frames();
if (console_stop_requested()) {
@ -2114,6 +2254,9 @@ int run(const Config& cfg) {
}
}
append_dout_word(false);
flush_dout_queue(cfg.recv_timeout_ms, true);
expect_ok(api, api.StreamsStop(device.hnd), "Stop streams");
device.streams_started = false;

View File

@ -28,6 +28,7 @@ exec "$BIN" \
di1_group_avg \
duration_ms:100 \
packet_limit:0 \
do1_toggle_per_frame \
profile:amplitude \
"tty:${TTY_PATH}" \
"$@"