ampl adc
This commit is contained in:
158
main.cpp
158
main.cpp
@ -55,10 +55,17 @@ enum class Di1Mode {
|
||||
Ignore
|
||||
};
|
||||
|
||||
enum class CaptureProfile {
|
||||
Manual,
|
||||
Phase,
|
||||
Amplitude
|
||||
};
|
||||
|
||||
struct Config {
|
||||
std::string serial;
|
||||
std::optional<uint32_t> ip_addr;
|
||||
|
||||
CaptureProfile profile = CaptureProfile::Manual;
|
||||
uint32_t channel_count = 2;
|
||||
uint32_t mode = X502_LCH_MODE_DIFF;
|
||||
uint32_t range = X502_ADC_RANGE_02;
|
||||
@ -90,6 +97,10 @@ struct Config {
|
||||
bool input_buffer_specified = false;
|
||||
bool input_step_specified = false;
|
||||
bool live_update_specified = false;
|
||||
bool mode_specified = false;
|
||||
bool channel_count_specified = false;
|
||||
bool ch1_specified = false;
|
||||
bool ch2_specified = false;
|
||||
|
||||
bool pullup_syn1 = false;
|
||||
bool pullup_syn2 = false;
|
||||
@ -222,6 +233,81 @@ uint32_t parse_mode(const std::string& text) {
|
||||
fail("Unsupported input mode: " + text);
|
||||
}
|
||||
|
||||
CaptureProfile parse_profile(const std::string& text) {
|
||||
const std::string value = trim_copy(text);
|
||||
if (value == "phase") {
|
||||
return CaptureProfile::Phase;
|
||||
}
|
||||
if (value == "amplitude") {
|
||||
return CaptureProfile::Amplitude;
|
||||
}
|
||||
fail("Unsupported profile: " + text + ". Use phase or amplitude");
|
||||
}
|
||||
|
||||
std::string profile_to_string(CaptureProfile profile) {
|
||||
switch (profile) {
|
||||
case CaptureProfile::Manual:
|
||||
return "manual";
|
||||
case CaptureProfile::Phase:
|
||||
return "phase";
|
||||
case CaptureProfile::Amplitude:
|
||||
return "amplitude";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
struct ProfileSettings {
|
||||
uint32_t channel_count = 2;
|
||||
uint32_t mode = X502_LCH_MODE_DIFF;
|
||||
uint32_t ch1 = 2;
|
||||
uint32_t ch2 = 3;
|
||||
};
|
||||
|
||||
ProfileSettings profile_settings(CaptureProfile profile) {
|
||||
switch (profile) {
|
||||
case CaptureProfile::Phase:
|
||||
return {2U, X502_LCH_MODE_DIFF, 2U, 3U};
|
||||
case CaptureProfile::Amplitude:
|
||||
return {1U, X502_LCH_MODE_COMM, 0U, 0U};
|
||||
case CaptureProfile::Manual:
|
||||
default:
|
||||
fail("Internal error: no settings for manual profile");
|
||||
}
|
||||
}
|
||||
|
||||
void apply_profile(Config& cfg) {
|
||||
if (cfg.profile == CaptureProfile::Manual) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ProfileSettings settings = profile_settings(cfg.profile);
|
||||
const std::string profile_name = profile_to_string(cfg.profile);
|
||||
const std::string profile_arg = "profile:" + profile_name;
|
||||
|
||||
if (cfg.mode_specified && (cfg.mode != settings.mode)) {
|
||||
fail(profile_arg + " conflicts with explicit mode");
|
||||
}
|
||||
if (cfg.channel_count_specified && (cfg.channel_count != settings.channel_count)) {
|
||||
fail(profile_arg + " conflicts with explicit channels");
|
||||
}
|
||||
if (cfg.ch1_specified && (cfg.ch1 != settings.ch1)) {
|
||||
fail(profile_arg + " conflicts with explicit ch1");
|
||||
}
|
||||
if (settings.channel_count <= 1U) {
|
||||
if (cfg.ch2_specified) {
|
||||
fail(profile_arg + " does not allow ch2");
|
||||
}
|
||||
} else if (cfg.ch2_specified && (cfg.ch2 != settings.ch2)) {
|
||||
fail(profile_arg + " conflicts with explicit ch2");
|
||||
}
|
||||
|
||||
cfg.mode = settings.mode;
|
||||
cfg.channel_count = settings.channel_count;
|
||||
cfg.ch1 = settings.ch1;
|
||||
cfg.ch2 = settings.ch2;
|
||||
}
|
||||
|
||||
uint32_t parse_internal_ref_freq(const std::string& text) {
|
||||
const std::string value = trim_copy(text);
|
||||
if ((value == "2000000") || (value == "2000khz") || (value == "2mhz")) {
|
||||
@ -376,7 +462,8 @@ 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] [channels:2] [ch1:2] [ch2:3]\n"
|
||||
<< " " << exe_name << " [serial:SN] [ip:192.168.0.10] [profile:phase|amplitude]\n"
|
||||
<< " [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"
|
||||
@ -415,11 +502,16 @@ void print_help(const char* exe_name) {
|
||||
<< " 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"
|
||||
<< " tty:/tmp/ttyADC_data -> write a continuous legacy 4-word CH1/CH2 stream to a Linux/POSIX tty or PTY link path\n"
|
||||
<< " di1_group_avg -> with tty + di1:trace, emit one averaged CH1/CH2 step per constant DI1 run\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"
|
||||
<< " If sample_clock_hz is omitted together with clock:internal, the maximum ADC speed is used\n"
|
||||
<< "\n"
|
||||
<< "Profiles:\n"
|
||||
<< " profile:phase -> mode:diff channels:2 ch1:2 ch2:3 (X3-Y3 and X4-Y4)\n"
|
||||
<< " profile:amplitude -> mode:comm channels:1 ch1:0 (X1, raw AD8317 voltage)\n"
|
||||
<< " profile:* is strict -> conflicting mode/channels/ch1/ch2 arguments are rejected\n"
|
||||
<< "\n"
|
||||
<< "Differential physical channel mapping:\n"
|
||||
<< " 0..15 -> X1-Y1 .. X16-Y16\n"
|
||||
<< "\n"
|
||||
@ -439,20 +531,21 @@ void print_help(const char* exe_name) {
|
||||
<< "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"
|
||||
<< "Amplitude mode keeps the raw AD8317 voltage as captured on X1; no inversion is applied.\n"
|
||||
<< "\n"
|
||||
<< "Recommended working example:\n"
|
||||
<< "Phase profile example:\n"
|
||||
<< " " << exe_name
|
||||
<< " clock:di_syn1_rise start:di_syn2_rise stop:di_syn2_fall sample_clock_hz:125000"
|
||||
<< " profile:phase clock:di_syn1_rise start:di_syn2_rise stop:di_syn2_fall sample_clock_hz:125000"
|
||||
<< " duration_ms:100 packet_limit:10 csv:chirp.csv svg:chirp.svg\n"
|
||||
<< "\n"
|
||||
<< "Internal-clock example:\n"
|
||||
<< "Amplitude profile example:\n"
|
||||
<< " " << exe_name
|
||||
<< " clock:internal internal_ref_hz:2000000 start:di_syn2_rise stop:di_syn2_fall"
|
||||
<< " sample_clock_hz:max duration_ms:100 packet_limit:0 csv:chirp.csv svg:chirp.svg\n"
|
||||
<< " profile:amplitude clock:internal internal_ref_hz:2000000 start:di_syn2_rise stop:di_syn2_fall"
|
||||
<< " sample_clock_hz:max duration_ms:100 packet_limit:0 csv:amplitude.csv svg:amplitude.svg\n"
|
||||
<< "\n"
|
||||
<< "TTY DI1-group average example:\n"
|
||||
<< "Amplitude TTY DI1-group average example:\n"
|
||||
<< " " << exe_name
|
||||
<< " 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"
|
||||
<< " tty:/tmp/ttyADC_data\n";
|
||||
}
|
||||
@ -490,12 +583,18 @@ Config parse_args(int argc, char** argv) {
|
||||
cfg.ip_addr = parse_ipv4(arg.substr(3));
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "profile:")) {
|
||||
cfg.profile = parse_profile(arg.substr(8));
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "mode:")) {
|
||||
cfg.mode = parse_mode(arg.substr(5));
|
||||
cfg.mode_specified = true;
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "channels:")) {
|
||||
cfg.channel_count = parse_channel_count(arg.substr(9));
|
||||
cfg.channel_count_specified = true;
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "range:")) {
|
||||
@ -504,10 +603,12 @@ Config parse_args(int argc, char** argv) {
|
||||
}
|
||||
if (starts_with(arg, "ch1:")) {
|
||||
cfg.ch1 = parse_u32(arg.substr(4), "ch1");
|
||||
cfg.ch1_specified = true;
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "ch2:")) {
|
||||
cfg.ch2 = parse_u32(arg.substr(4), "ch2");
|
||||
cfg.ch2_specified = true;
|
||||
continue;
|
||||
}
|
||||
if (starts_with(arg, "clock:")) {
|
||||
@ -612,6 +713,8 @@ Config parse_args(int argc, char** argv) {
|
||||
fail("Unknown argument: " + arg);
|
||||
}
|
||||
|
||||
apply_profile(cfg);
|
||||
|
||||
if (cfg.duration_ms <= 0.0) {
|
||||
fail("duration_ms must be > 0");
|
||||
}
|
||||
@ -662,12 +765,6 @@ 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.tty_path && cfg.di1_group_average && (cfg.channel_count != 2U)) {
|
||||
fail("tty di1_group_avg requires channels:2");
|
||||
}
|
||||
if (cfg.tty_path && (cfg.channel_count != 2U)) {
|
||||
fail("tty output 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");
|
||||
}
|
||||
@ -1059,8 +1156,8 @@ struct TtyGroupAverageState {
|
||||
}
|
||||
}
|
||||
|
||||
bool has_complete_step() const {
|
||||
return (count_ch1 != 0U) && (count_ch2 != 0U);
|
||||
bool has_complete_step(uint32_t channel_count) const {
|
||||
return (count_ch1 != 0U) && ((channel_count <= 1U) || (count_ch2 != 0U));
|
||||
}
|
||||
};
|
||||
|
||||
@ -1494,7 +1591,7 @@ int run(const Config& cfg) {
|
||||
};
|
||||
|
||||
auto append_tty_group_step = [&]() {
|
||||
if (!tty_di1_group_average || !tty_group_state.has_complete_step()) {
|
||||
if (!tty_di1_group_average || !tty_group_state.has_complete_step(cfg.channel_count)) {
|
||||
return;
|
||||
}
|
||||
if (tty_group_state.next_index >= 0xFFFFU) {
|
||||
@ -1502,7 +1599,10 @@ int run(const Config& cfg) {
|
||||
}
|
||||
|
||||
const double ch1_avg = tty_group_state.sum_ch1 / static_cast<double>(tty_group_state.count_ch1);
|
||||
const double ch2_avg = tty_group_state.sum_ch2 / static_cast<double>(tty_group_state.count_ch2);
|
||||
const double ch2_avg =
|
||||
(cfg.channel_count <= 1U)
|
||||
? 0.0
|
||||
: (tty_group_state.sum_ch2 / static_cast<double>(tty_group_state.count_ch2));
|
||||
append_tty_frame(0x000A,
|
||||
static_cast<uint16_t>(tty_group_state.next_index),
|
||||
static_cast<uint16_t>(pack_raw_code_to_int16(ch1_avg)),
|
||||
@ -1760,9 +1860,25 @@ int run(const Config& cfg) {
|
||||
|
||||
if (!tty_di1_group_average) {
|
||||
tty_frame_words.clear();
|
||||
tty_frame_words.reserve(((static_cast<std::size_t>(raw_adc_count) + 1U) / 2U) * 4U);
|
||||
const std::size_t tty_frame_count =
|
||||
(cfg.channel_count <= 1U)
|
||||
? static_cast<std::size_t>(raw_adc_count)
|
||||
: ((static_cast<std::size_t>(raw_adc_count) + 1U) / 2U);
|
||||
tty_frame_words.reserve(tty_frame_count * 4U);
|
||||
for (uint32_t i = 0; i < raw_adc_count; ++i) {
|
||||
const int16_t sample = pack_raw_code_to_int16(adc_raw_buffer[i]);
|
||||
if (cfg.channel_count <= 1U) {
|
||||
append_tty_frame(0x000A,
|
||||
tty_state.next_index,
|
||||
static_cast<uint16_t>(sample),
|
||||
0U);
|
||||
if (tty_state.next_index >= 0xFFFEU) {
|
||||
tty_state.next_index = 1U;
|
||||
} else {
|
||||
++tty_state.next_index;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!tty_state.pending_ch1_valid) {
|
||||
tty_state.pending_ch1 = sample;
|
||||
tty_state.pending_ch1_valid = true;
|
||||
|
||||
Reference in New Issue
Block a user