Add single-channel capture mode
This commit is contained in:
165
main.cpp
165
main.cpp
@ -44,8 +44,9 @@ struct Config {
|
||||
std::string serial;
|
||||
std::optional<uint32_t> ip_addr;
|
||||
|
||||
uint32_t channel_count = 2;
|
||||
uint32_t mode = X502_LCH_MODE_DIFF;
|
||||
uint32_t range = X502_ADC_RANGE_5;
|
||||
uint32_t range = X502_ADC_RANGE_02;
|
||||
uint32_t ch1 = 2;
|
||||
uint32_t ch2 = 3;
|
||||
|
||||
@ -60,12 +61,13 @@ struct Config {
|
||||
uint32_t sync_start_mode = X502_SYNC_DI_SYN2_RISE;
|
||||
StopMode stop_mode = StopMode::DiSyn2Fall;
|
||||
|
||||
uint32_t recv_block_words = 8192;
|
||||
uint32_t recv_timeout_ms = 100;
|
||||
uint32_t recv_block_words = 4096;
|
||||
uint32_t recv_timeout_ms = 50;
|
||||
uint32_t stats_period_ms = 1000;
|
||||
uint32_t start_wait_ms = 10000;
|
||||
uint32_t input_buffer_words = 262144;
|
||||
uint32_t input_step_words = 8192;
|
||||
uint32_t input_buffer_words = 4 * 1024 * 1024;
|
||||
uint32_t input_step_words = 4096;
|
||||
uint32_t live_update_period_ms = 500;
|
||||
|
||||
bool pullup_syn1 = false;
|
||||
bool pullup_syn2 = false;
|
||||
@ -207,6 +209,14 @@ uint32_t parse_internal_ref_freq(const std::string& text) {
|
||||
fail("Unsupported internal_ref_hz value: " + text + ". Use 1500000 or 2000000");
|
||||
}
|
||||
|
||||
uint32_t parse_channel_count(const std::string& text) {
|
||||
const uint32_t value = parse_u32(text, "channels");
|
||||
if ((value == 1U) || (value == 2U)) {
|
||||
return value;
|
||||
}
|
||||
fail("channels must be 1 or 2");
|
||||
}
|
||||
|
||||
std::string ref_freq_to_string(uint32_t freq) {
|
||||
switch (freq) {
|
||||
case X502_REF_FREQ_2000KHZ:
|
||||
@ -315,19 +325,22 @@ std::string phy_channel_name(uint32_t mode, uint32_t phy_ch) {
|
||||
void print_help(const char* exe_name) {
|
||||
std::cout
|
||||
<< "Usage:\n"
|
||||
<< " " << exe_name << " [serial:SN] [ip:192.168.0.10] [ch1:2] [ch2:3]\n"
|
||||
<< " [mode:diff|comm] [range:5] [clock:di_syn1_rise]\n"
|
||||
<< " " << exe_name << " [serial:SN] [ip:192.168.0.10] [channels:2] [ch1:2] [ch2:3]\n"
|
||||
<< " [mode:diff|comm] [range:0.2] [clock:di_syn1_rise]\n"
|
||||
<< " [start:di_syn2_rise] [stop:di_syn2_fall] [sample_clock_hz:125000|max]\n"
|
||||
<< " [internal_ref_hz:2000000]\n"
|
||||
<< " [duration_ms:100] [packet_limit:0] [csv:capture.csv] [svg:capture.svg]\n"
|
||||
<< " [live_html:live_plot.html] [live_json:live_plot.json]\n"
|
||||
<< " [recv_block:8192] [stats_period_ms:1000] [start_wait_ms:10000]\n"
|
||||
<< " [recv_block:4096] [stats_period_ms:1000] [live_update_period_ms:500] [start_wait_ms:10000]\n"
|
||||
<< " [pullup_syn1] [pullup_syn2] [pulldown_conv_in] [pulldown_start_in]\n"
|
||||
<< "\n"
|
||||
<< "Defaults for E-502:\n"
|
||||
<< " channels:2 -> capture CH1 and CH2\n"
|
||||
<< " ch1:2, ch2:3 -> X3-Y3 and X4-Y4\n"
|
||||
<< " mode:diff -> differential measurement\n"
|
||||
<< " range:5 -> +/-5 V range\n"
|
||||
<< " range:0.2 -> +/-0.2 V range\n"
|
||||
<< " supported ranges -> 10, 5, 2, 1, 0.5, 0.2 V\n"
|
||||
<< " channels:1 -> capture only CH1, CH2 is ignored\n"
|
||||
<< " clock:di_syn1_rise-> external sample clock on DI_SYN1 rising edge\n"
|
||||
<< " clock:internal -> module generates its own clock\n"
|
||||
<< " start:di_syn2_rise-> packet starts on DI_SYN2 rising edge\n"
|
||||
@ -336,6 +349,7 @@ void print_help(const char* exe_name) {
|
||||
<< " sample_clock_hz:max -> with clock:internal, use the maximum ADC speed\n"
|
||||
<< " internal_ref_hz:2000000 -> internal base clock for clock:internal (1500000 or 2000000)\n"
|
||||
<< " stats_period_ms:1000 -> print online transfer/capture statistics every 1000 ms (0 disables)\n"
|
||||
<< " live_update_period_ms:500 -> refresh live HTML/JSON no more than twice per second\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"
|
||||
<< " live_html/live_json -> live graph files updated as packets arrive\n"
|
||||
@ -406,6 +420,10 @@ Config parse_args(int argc, char** argv) {
|
||||
cfg.mode = parse_mode(arg.substr(5));
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "channels:")) {
|
||||
cfg.channel_count = parse_channel_count(arg.substr(9));
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "range:")) {
|
||||
cfg.range = parse_range(arg.substr(6));
|
||||
continue;
|
||||
@ -465,6 +483,10 @@ Config parse_args(int argc, char** argv) {
|
||||
cfg.stats_period_ms = parse_u32(arg.substr(16), "stats_period_ms");
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "live_update_period_ms:")) {
|
||||
cfg.live_update_period_ms = parse_u32(arg.substr(22), "live_update_period_ms");
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "start_wait_ms:")) {
|
||||
cfg.start_wait_ms = parse_u32(arg.substr(14), "start_wait_ms");
|
||||
continue;
|
||||
@ -531,12 +553,18 @@ Config parse_args(int argc, char** argv) {
|
||||
}
|
||||
|
||||
if (cfg.mode == X502_LCH_MODE_DIFF) {
|
||||
if ((cfg.ch1 >= X502_ADC_DIFF_CH_CNT) || (cfg.ch2 >= X502_ADC_DIFF_CH_CNT)) {
|
||||
fail("For differential mode E-502 channels must be in range 0..15");
|
||||
if (cfg.ch1 >= X502_ADC_DIFF_CH_CNT) {
|
||||
fail("For differential mode E-502 ch1 must be in range 0..15");
|
||||
}
|
||||
if ((cfg.channel_count >= 2U) && (cfg.ch2 >= X502_ADC_DIFF_CH_CNT)) {
|
||||
fail("For differential mode E-502 ch2 must be in range 0..15");
|
||||
}
|
||||
} else {
|
||||
if ((cfg.ch1 >= X502_ADC_COMM_CH_CNT) || (cfg.ch2 >= X502_ADC_COMM_CH_CNT)) {
|
||||
fail("For common-ground mode E-502 channels must be in range 0..31");
|
||||
if (cfg.ch1 >= X502_ADC_COMM_CH_CNT) {
|
||||
fail("For common-ground mode E-502 ch1 must be in range 0..31");
|
||||
}
|
||||
if ((cfg.channel_count >= 2U) && (cfg.ch2 >= X502_ADC_COMM_CH_CNT)) {
|
||||
fail("For common-ground mode E-502 ch2 must be in range 0..31");
|
||||
}
|
||||
}
|
||||
|
||||
@ -719,17 +747,20 @@ struct PacketAccumulator {
|
||||
std::size_t zeroed_samples = 0;
|
||||
std::size_t stored_samples = 0;
|
||||
|
||||
void reset(std::size_t reserve_frames) {
|
||||
for (auto& channel : channels) {
|
||||
void reset(std::size_t reserve_frames, std::size_t channel_count) {
|
||||
for (std::size_t i = 0; i < channels.size(); ++i) {
|
||||
auto& channel = channels[i];
|
||||
channel.clear();
|
||||
channel.reserve(reserve_frames);
|
||||
if (i < channel_count) {
|
||||
channel.reserve(reserve_frames);
|
||||
}
|
||||
}
|
||||
zeroed_samples = 0;
|
||||
stored_samples = 0;
|
||||
}
|
||||
|
||||
std::size_t frame_count() const {
|
||||
return std::min(channels[0].size(), channels[1].size());
|
||||
std::size_t frame_count(std::size_t channel_count) const {
|
||||
return (channel_count <= 1U) ? channels[0].size() : std::min(channels[0].size(), channels[1].size());
|
||||
}
|
||||
};
|
||||
|
||||
@ -824,9 +855,11 @@ int run(const Config& cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
expect_ok(api, api.SetLChannelCount(device.hnd, 2), "Set logical channel count");
|
||||
expect_ok(api, api.SetLChannelCount(device.hnd, cfg.channel_count), "Set logical channel count");
|
||||
expect_ok(api, api.SetLChannel(device.hnd, 0, cfg.ch1, cfg.mode, cfg.range, 1), "Set logical channel 0");
|
||||
expect_ok(api, api.SetLChannel(device.hnd, 1, cfg.ch2, cfg.mode, cfg.range, 1), "Set logical channel 1");
|
||||
if (cfg.channel_count >= 2U) {
|
||||
expect_ok(api, api.SetLChannel(device.hnd, 1, cfg.ch2, cfg.mode, cfg.range, 1), "Set logical channel 1");
|
||||
}
|
||||
|
||||
const bool internal_max_clock = use_internal_max_clock(cfg);
|
||||
double actual_sample_clock_hz = cfg.sample_clock_hz;
|
||||
@ -887,13 +920,18 @@ int run(const Config& cfg) {
|
||||
}
|
||||
std::cout << "\n"
|
||||
<< " DIN clock: " << actual_din_freq_hz << " Hz\n"
|
||||
<< " ADC logical channels: " << cfg.channel_count << "\n"
|
||||
<< " per-channel frame rate: " << actual_frame_freq_hz << " Hz\n"
|
||||
<< " duration: " << cfg.duration_ms << " ms\n"
|
||||
<< " packet limit: "
|
||||
<< ((cfg.packet_limit == 0U) ? std::string("continuous until Ctrl+C")
|
||||
: std::to_string(cfg.packet_limit) + " packet(s)") << "\n"
|
||||
<< " live update period: "
|
||||
<< ((cfg.live_update_period_ms == 0U) ? std::string("every packet")
|
||||
: std::to_string(cfg.live_update_period_ms) + " ms") << "\n"
|
||||
<< " channel 1: " << phy_channel_name(cfg.mode, cfg.ch1) << "\n"
|
||||
<< " channel 2: " << phy_channel_name(cfg.mode, cfg.ch2) << "\n"
|
||||
<< " channel 2: "
|
||||
<< ((cfg.channel_count >= 2U) ? phy_channel_name(cfg.mode, cfg.ch2) : std::string("disabled")) << "\n"
|
||||
<< " ADC range: +/-" << range_to_volts(cfg.range) << " V\n"
|
||||
<< " max frames per packet per channel: " << target_frames << "\n";
|
||||
|
||||
@ -920,7 +958,7 @@ int run(const Config& cfg) {
|
||||
packets.reserve(cfg.packet_limit);
|
||||
}
|
||||
PacketAccumulator current_packet;
|
||||
current_packet.reset(target_frames);
|
||||
current_packet.reset(target_frames, cfg.channel_count);
|
||||
|
||||
bool capture_started = false;
|
||||
bool stop_loop_requested = false;
|
||||
@ -935,6 +973,7 @@ int run(const Config& cfg) {
|
||||
const ULONGLONG capture_loop_start = GetTickCount64();
|
||||
ULONGLONG stats_window_start = capture_loop_start;
|
||||
ULONGLONG last_stats_print = capture_loop_start;
|
||||
ULONGLONG last_live_update = 0;
|
||||
|
||||
uint64_t total_raw_words = 0;
|
||||
uint64_t total_adc_samples = 0;
|
||||
@ -990,18 +1029,23 @@ int run(const Config& cfg) {
|
||||
if (packet_active) {
|
||||
return;
|
||||
}
|
||||
current_packet.reset(target_frames);
|
||||
current_packet.reset(target_frames, cfg.channel_count);
|
||||
packet_active = true;
|
||||
};
|
||||
|
||||
auto finish_packet = [&](PacketCloseReason reason) {
|
||||
const std::size_t frames = current_packet.frame_count();
|
||||
const std::size_t frames = current_packet.frame_count(cfg.channel_count);
|
||||
if (frames != 0U) {
|
||||
current_packet.channels[0].resize(frames);
|
||||
current_packet.channels[1].resize(frames);
|
||||
if (cfg.channel_count >= 2U) {
|
||||
current_packet.channels[1].resize(frames);
|
||||
} else {
|
||||
current_packet.channels[1].clear();
|
||||
}
|
||||
|
||||
CapturePacket packet;
|
||||
packet.packet_index = packets.size() + 1U;
|
||||
packet.channel_count = cfg.channel_count;
|
||||
packet.ch1 = std::move(current_packet.channels[0]);
|
||||
packet.ch2 = std::move(current_packet.channels[1]);
|
||||
|
||||
@ -1024,20 +1068,35 @@ int run(const Config& cfg) {
|
||||
++stats_completed_packets;
|
||||
const double elapsed_capture_s =
|
||||
std::max(1e-9, static_cast<double>(GetTickCount64() - capture_loop_start) / 1000.0);
|
||||
const double packets_per_second = static_cast<double>(total_completed_packets) / elapsed_capture_s;
|
||||
writer.update_live_plot(packets.back(),
|
||||
packets.size(),
|
||||
packets_per_second,
|
||||
actual_frame_freq_hz,
|
||||
packet_close_reason_to_string(reason),
|
||||
current_packet.zeroed_samples,
|
||||
current_packet.stored_samples);
|
||||
const double packets_per_second =
|
||||
(elapsed_capture_s >= 0.1)
|
||||
? (static_cast<double>(total_completed_packets) / elapsed_capture_s)
|
||||
: 0.0;
|
||||
const ULONGLONG now = GetTickCount64();
|
||||
const bool should_update_live =
|
||||
(cfg.live_update_period_ms == 0U) ||
|
||||
(last_live_update == 0U) ||
|
||||
((now - last_live_update) >= cfg.live_update_period_ms);
|
||||
if (should_update_live) {
|
||||
writer.update_live_plot(packets.back(),
|
||||
packets.size(),
|
||||
packets_per_second,
|
||||
actual_frame_freq_hz,
|
||||
packet_close_reason_to_string(reason),
|
||||
current_packet.zeroed_samples,
|
||||
current_packet.stored_samples);
|
||||
last_live_update = now;
|
||||
}
|
||||
std::cout << std::fixed << std::setprecision(3)
|
||||
<< " packets/s(avg)=" << packets_per_second << "\n";
|
||||
<< " packets/s(avg)=" << packets_per_second;
|
||||
if (elapsed_capture_s < 0.1) {
|
||||
std::cout << " (warming up)";
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
packet_active = false;
|
||||
current_packet.reset(target_frames);
|
||||
current_packet.reset(target_frames, cfg.channel_count);
|
||||
};
|
||||
|
||||
while (!stop_loop_requested) {
|
||||
@ -1088,16 +1147,22 @@ int run(const Config& cfg) {
|
||||
|
||||
uint32_t adc_count = static_cast<uint32_t>(adc_buffer.size());
|
||||
uint32_t din_count = static_cast<uint32_t>(din_buffer.size());
|
||||
expect_ok(api,
|
||||
api.ProcessData(device.hnd,
|
||||
raw.data(),
|
||||
static_cast<uint32_t>(recvd),
|
||||
X502_PROC_FLAGS_VOLT,
|
||||
adc_buffer.data(),
|
||||
&adc_count,
|
||||
din_buffer.data(),
|
||||
&din_count),
|
||||
"Process ADC+DIN data");
|
||||
const int32_t process_err = api.ProcessData(device.hnd,
|
||||
raw.data(),
|
||||
static_cast<uint32_t>(recvd),
|
||||
X502_PROC_FLAGS_VOLT,
|
||||
adc_buffer.data(),
|
||||
&adc_count,
|
||||
din_buffer.data(),
|
||||
&din_count);
|
||||
if (process_err == X502_ERR_STREAM_OVERFLOW) {
|
||||
std::ostringstream message;
|
||||
message << "Process ADC+DIN data: " << x502_error(api, process_err)
|
||||
<< ". Try larger buffer_words/step_words, a longer live_update_period_ms, "
|
||||
<< "or a lower sample_clock_hz / DIN load.";
|
||||
fail(message.str());
|
||||
}
|
||||
expect_ok(api, process_err, "Process ADC+DIN data");
|
||||
|
||||
if ((adc_count == 0U) && (din_count == 0U)) {
|
||||
continue;
|
||||
@ -1182,7 +1247,7 @@ int run(const Config& cfg) {
|
||||
}
|
||||
|
||||
const uint32_t lch = next_lch;
|
||||
next_lch = (next_lch + 1U) % 2U;
|
||||
next_lch = (next_lch + 1U) % cfg.channel_count;
|
||||
|
||||
double stored_value = adc_value;
|
||||
if (zero_on_di1_change) {
|
||||
@ -1197,13 +1262,13 @@ int run(const Config& cfg) {
|
||||
++current_packet.stored_samples;
|
||||
++total_stored_adc_samples;
|
||||
++stats_stored_adc_samples;
|
||||
if (lch == 1U) {
|
||||
if (lch == (cfg.channel_count - 1U)) {
|
||||
++total_completed_frames;
|
||||
++stats_completed_frames;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_packet.frame_count() >= target_frames) {
|
||||
if (current_packet.frame_count(cfg.channel_count) >= target_frames) {
|
||||
finish_packet(PacketCloseReason::DurationLimit);
|
||||
if ((cfg.packet_limit != 0U) && (packets.size() >= cfg.packet_limit)) {
|
||||
stop_loop_requested = true;
|
||||
@ -1236,7 +1301,9 @@ int run(const Config& cfg) {
|
||||
|
||||
std::size_t total_packet_frames = 0;
|
||||
for (const auto& packet : packets) {
|
||||
total_packet_frames += std::min(packet.ch1.size(), packet.ch2.size());
|
||||
total_packet_frames += (packet.channel_count <= 1U)
|
||||
? packet.ch1.size()
|
||||
: std::min(packet.ch1.size(), packet.ch2.size());
|
||||
}
|
||||
|
||||
std::cout << "Captured " << packets.size() << " packet(s), "
|
||||
|
||||
Reference in New Issue
Block a user