diff --git a/Makefile.am b/Makefile.am index dea187344..c2e5ee1c6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -653,6 +653,12 @@ src_libdrivers_la_SOURCES += \ src/hardware/sysclk-sla5032/protocol.c \ src/hardware/sysclk-sla5032/api.c endif +if HW_TEKTRONIX_TDS +src_libdrivers_la_SOURCES += \ + src/hardware/tektronix-tds/protocol.h \ + src/hardware/tektronix-tds/protocol.c \ + src/hardware/tektronix-tds/api.c +endif if HW_TELEINFO src_libdrivers_la_SOURCES += \ src/hardware/teleinfo/protocol.h \ diff --git a/configure.ac b/configure.ac index ea386c0d3..f43cc5d54 100644 --- a/configure.ac +++ b/configure.ac @@ -359,6 +359,7 @@ SR_DRIVER([serial LCR], [serial-lcr], [serial_comm]) SR_DRIVER([Siglent SDS], [siglent-sds]) SR_DRIVER([Sysclk LWLA], [sysclk-lwla], [libusb]) SR_DRIVER([Sysclk SLA5032], [sysclk-sla5032], [libusb]) +SR_DRIVER([Tektronix TDS], [tektronix-tds]) SR_DRIVER([Teleinfo], [teleinfo], [serial_comm]) SR_DRIVER([Testo], [testo], [libusb]) SR_DRIVER([Tondaj SL-814], [tondaj-sl-814], [serial_comm]) diff --git a/contrib/60-libsigrok.rules b/contrib/60-libsigrok.rules index 398ee4040..a0435ec86 100644 --- a/contrib/60-libsigrok.rules +++ b/contrib/60-libsigrok.rules @@ -313,6 +313,9 @@ ATTRS{idVendor}=="2961", ATTRS{idProduct}=="6689", ENV{ID_SIGROK}="1" # Sysclk SLA5032 ("32CH 500M" mode) ATTRS{idVendor}=="2961", ATTRS{idProduct}=="66b0", ENV{ID_SIGROK}="1" +# Tektronix +ATTRS{idVendor}=="0699", ATTRS{idProduct}=="036a", ENV{ID_SIGROK}="1" + # Testo 435 ATTRS{idVendor}=="128d", ATTRS{idProduct}=="0003", ENV{ID_SIGROK}="1" diff --git a/src/hardware/tektronix-tds/api.c b/src/hardware/tektronix-tds/api.c new file mode 100644 index 000000000..92d182159 --- /dev/null +++ b/src/hardware/tektronix-tds/api.c @@ -0,0 +1,966 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Patrick Plenefisch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include "libsigrok-internal.h" +#include "protocol.h" +#include "scpi.h" + +/** + * Documentation for the SCPI commands can be found in + * https://download.tek.com/manual/TBS1000-B-EDU-TDS2000-B-C-TDS1000-B-C-EDU-TDS200-TPS2000-B-Programmer-077044403_RevB.pdf + * and is referred to as "doc page $PDF_PAGE/$PRINTED_PAGE" + */ + + +/** + * Missing semi-important features: + * bandwidth limiting ch:bandwidth + * chanel invert ch:invert + * volt/amp configuration ch::yunit + * pulse triggering + * ext trigger coupling + * peak-detect mode (data retreival) + * + * Missing less important features: + * capture/savefiles + * screenshots + * fine adjust of vdivs + * video triggering + */ + + +static struct sr_dev_driver tektronix_tds_driver_info; + +static const uint32_t scanopts[] = {SR_CONF_CONN, SR_CONF_SERIALCOMM}; + +static const uint32_t drvopts[] = {SR_CONF_OSCILLOSCOPE}; + +static const uint32_t devopts[] = { + SR_CONF_TIMEBASE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_TRIGGER_SOURCE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_TRIGGER_SLOPE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_TRIGGER_LEVEL | SR_CONF_GET | SR_CONF_SET, + SR_CONF_HORIZ_TRIGGERPOS | SR_CONF_GET | SR_CONF_SET, + SR_CONF_CAPTURE_RATIO | SR_CONF_GET | SR_CONF_SET, + SR_CONF_NUM_HDIV | SR_CONF_GET, + SR_CONF_SAMPLERATE | SR_CONF_GET, + SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_AVERAGING | SR_CONF_GET | SR_CONF_SET, + SR_CONF_AVG_SAMPLES | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_BUFFERSIZE | SR_CONF_GET, + SR_CONF_DATA_SOURCE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_PEAK_DETECTION | SR_CONF_GET | SR_CONF_SET, +}; + +static const uint32_t devopts_cg_analog[] = { + SR_CONF_NUM_VDIV | SR_CONF_GET, + SR_CONF_VDIV | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_COUPLING | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_PROBE_FACTOR | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET, +}; + +// TODO: Compensation adjusts the vdivs, but PulseView doesn't pick up +// any such dynamic changes, nor does it seem to support fine-adjust, +// so for now all possible compensation vdivs are present + +// validated in doc page 75/2-57 +static const uint64_t vdivs[][2] = { + /* millivolts */ + {2, 1000}, + {5, 1000}, + {10, 1000}, + {20, 1000}, + {50, 1000}, + {100, 1000}, + {200, 1000}, + {500, 1000}, + /* volts */ + {1, 1}, + {2, 1}, + {5, 1}, + /* >1x compensation only */ + {10, 1}, + {20, 1}, + {50, 1}, + /* >10x compensation only */ + {100, 1}, + {200, 1}, + {500, 1}, + /* >100x compensation only */ + {1000, 1}, + {2000, 1}, + {5000, 1}, +}; + +// everyone uses the same voltrange, only modified by compensation ranges (also the same) +#define VOLTRANGE_2m_5V 0, 0 + +static const uint64_t timebases[][2] = { + /* nanoseconds */ + {25, 10000000000}, + {5, 1000000000}, + {10, 1000000000}, + {25, 1000000000}, + {50, 1000000000}, + {100, 1000000000}, + {250, 1000000000}, + {500, 1000000000}, + /* microseconds */ + {1, 1000000}, + {25, 10000000}, + {5, 1000000}, + {10, 1000000}, + {25, 1000000}, + {50, 1000000}, + {100, 1000000}, + {250, 1000000}, + {500, 1000000}, + /* milliseconds */ + {1, 1000}, + {25, 10000}, + {5, 1000}, + {10, 1000}, + {25, 1000}, + {50, 1000}, + {100, 1000}, + {250, 1000}, + {500, 1000}, + /* seconds */ + {1, 1}, + {25, 10}, + {5, 1}, + {10, 1}, + {25, 1}, + {50, 1}, +}; +// Timebase limits are forward index, and reverse index +#define TIMEBASE_2n5_50s 0, 0 +#define TIMEBASE_5ns_50s 1, 0 +#define TIMEBASE_10n_50s 2, 0 +#define TIMEBASE_5ns_5s 1, 3 + +// validated in doc page 71/2-53 +static const char *coupling[] = { + "AC", + "DC", + "GND", +}; + +// validated in doc page 74/2-53 +static const uint64_t probe_factor_new[] = {1, 10, 20, 50, 100, 500, 1000}; +// Only for tds200, tds2000, tds1000 +static const uint64_t probe_factor_old[] = {1, 10, 100, 1000}; + +static const char *trigger_slopes[] = { + "r", + "f", +}; + +// validated in doc page 60/2-42 +static const uint64_t averages[] = {4, 16, 64, 128}; + +enum series_support_matrix +{ + T_S_Remainder, + TDS224, + TPS_2k, +}; + +// Must be in same order as the enum values DRIVER_CAPTURE_MODE +static const char *data_sources[] = { + [CAPTURE_LIVE] = "Live", + [CAPTURE_ONE_SHOT] = "One Shot", + [CAPTURE_DISPLAY] = "Memory+Live", + [CAPTURE_MEMORY] = "Memory", +}; + +// Note, CH3 and 4 should be last so that 4ch vs 2ch scopes +// can simply truncate this list by two +// validated in doc page 214/2-196 +static const char *trigger_sources_models_T_S_Remainder[] = { + "Ext", "Ext /5", "AC Line", + "CH1", "CH2", + /* 4ch only: */ "CH3", "CH4", +}; +static const char *trigger_sources_models_TDS224[] = { + "AC Line", + "CH1", "CH2", + /* 4ch only: */ "CH3", "CH4" +}; +static const char *trigger_sources_models_TPS_2k[] = { + "Ext", "Ext /5", "Ext /10", + "CH1", "CH2", + /* 4ch only: */ "CH3", "CH4", +}; + +#define DEVICE_SPEC(id_name, channels, sa_per_s, bw, probe_factors, \ + time_range, volt_range, trigger_sources) \ + { \ + id_name, channels, SA_##sa_per_s, BW_##bw, \ + ARRAY_AND_SIZE(probe_factor_##probe_factors), \ + TIMEBASE_##time_range, VOLTRANGE_##volt_range, \ + (trigger_sources_models_##trigger_sources), \ + ARRAY_SIZE(trigger_sources_models_##trigger_sources) - \ + 4 + channels \ + } + +/* This table was generated from the documentation: + * + * TBS1000B/EDU: https://download.tek.com/manual/TBS1000B-User-Manual-077088602-RevA.pdf + * TBS1000: https://download.tek.com/manual/TBS1000-Oscilloscope-User-Manual_077076001.pdf + * TDS2000C/TDS1000C-EDU: https://download.tek.com/manual/TDS2000C-and-TDS1000C-EDU-Oscilloscope-User-Manual-EN_077082600.pdf + * TDS2000B/TDS1000B: https://download.tek.com/manual/071181702web.pdf + * TDS2000/TDS1000: https://download.tek.com/manual/TDS2000_TDS1000_User_071106400_Revision_A.pdf + * TDS200: https://download.tek.com/manual/071039803.pdf + * TPS2000B:https://download.tek.com/manual/TPS2000B-Digital-Oscilloscope-User-Manual-077137901.pdf + * TPS2000: https://download.tek.com/manual/071144105web.pdf + * + * All specs can be found in Appendix A's of the linked pdfs + * EDU series are badge-only, and respond as if they are non-EDU products + */ +static const struct device_spec device_models[] = { + + // TBS original-series + DEVICE_SPEC("TBS 1022", 2, 500M, 25MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1042", 2, 500M, 40MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1062", 2, 1G, 60MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1064", 4, 1G, 60MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1102", 2, 1G, 100MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1104", 4, 1G, 100MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1152", 2, 1G, 150MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1154", 4, 1G, 150MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + + // TBS B-series + DEVICE_SPEC("TBS 1032B", 2, 500M, 30MHz, new, 10n_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1052B", 2, 1G, 50MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1072B", 2, 1G, 70MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1102B", 2, 2G, 100MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1152B", 2, 2G, 150MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TBS 1202B", 2, 2G, 200MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + + // TDS 200-series, only supports 5ns-5s/div + DEVICE_SPEC("TDS 210", 2, 1G , 60MHz, old, 5ns_5s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 220", 2, 1G, 100MHz, old, 5ns_5s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 224", 4, 1G, 100MHz, old, 5ns_5s, 2m_5V, TDS224), + + // TDS original-series + DEVICE_SPEC("TDS 1002", 2, 1G, 60MHz, old, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 1012", 2, 1G, 100MHz, old, 5ns_50s, 2m_5V, T_S_Remainder), + + DEVICE_SPEC("TDS 2002", 2, 1G, 60MHz, old, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2012", 2, 1G, 100MHz, old, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2014", 4, 1G, 100MHz, old, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2022", 2, 2G, 200MHz, old, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2024", 4, 2G, 200MHz, old, 2n5_50s, 2m_5V, T_S_Remainder), + + // TDS B-series + DEVICE_SPEC("TDS 1001B", 2, 500M, 40MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 1002B", 2, 1G, 60MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 1012B", 2, 1G, 100MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + + DEVICE_SPEC("TDS 2002B", 2, 1G, 60MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2004B", 4, 1G, 60MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2012B", 2, 1G, 100MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2014B", 4, 1G, 100MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2022B", 2, 2G, 200MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2024B", 4, 2G, 200MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + + // TDS C-series + DEVICE_SPEC("TDS 1001C", 2, 500M, 40MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 1002C", 2, 1G, 60MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 1012C", 2, 1G, 100MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + + DEVICE_SPEC("TDS 2001C", 2, 500M, 50MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2002C", 2, 1G, 70MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2004C", 4, 1G, 70MHz, new, 5ns_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2012C", 2, 2G, 100MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2014C", 4, 2G, 100MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2022C", 2, 2G, 200MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + DEVICE_SPEC("TDS 2024C", 4, 2G, 200MHz, new, 2n5_50s, 2m_5V, T_S_Remainder), + + // TPS original-series + DEVICE_SPEC("TPS 2012", 2, 1G, 100MHz, new, 5ns_50s, 2m_5V, TPS_2k), + DEVICE_SPEC("TPS 2014", 4, 1G, 100MHz, new, 5ns_50s, 2m_5V, TPS_2k), + DEVICE_SPEC("TPS 2024", 4, 2G, 200MHz, new, 2n5_50s, 2m_5V, TPS_2k), + + // TPS B-series + DEVICE_SPEC("TPS 2012B", 2, 1G, 100MHz, new, 5ns_50s, 2m_5V, TPS_2k), + DEVICE_SPEC("TPS 2014B", 4, 1G, 100MHz, new, 5ns_50s, 2m_5V, TPS_2k), + DEVICE_SPEC("TPS 2024B", 4, 2G, 200MHz, new, 2n5_50s, 2m_5V, TPS_2k), +}; + +static const char *TEKTRONIX = "Tektronix"; + +static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct sr_scpi_hw_info *hw_info; + const struct device_spec *device; + struct sr_channel *ch; + unsigned int i; + gchar *channel_name; + + sdi = NULL; + devc = NULL; + hw_info = NULL; + + // Slow devices (like TDS 220) need lots more time, though even fast + // deviecs require ~8 seconds for data xfer + scpi->read_timeout_us = 30 * 1000 * 1000; + + if (sr_scpi_get_hw_id(scpi, &hw_info) != SR_OK) { + sr_info("Couldn't get IDN response, retrying."); + sr_scpi_close(scpi); + sr_scpi_open(scpi); + if (sr_scpi_get_hw_id(scpi, &hw_info) != SR_OK) { + sr_info("Couldn't get IDN response."); + goto error; + } + } + + if (g_ascii_strcasecmp(hw_info->manufacturer, TEKTRONIX) != 0) + goto error; + + device = NULL; + for (i = 0; i < ARRAY_SIZE(device_models); i++) { + if (g_ascii_strcasecmp(hw_info->model, device_models[i].model) != 0) + continue; + device = &device_models[i]; + break; + } + + if (!device) { + sr_dbg("Found Tektronix device not supported by the tds/tps/tbs driver: %s", + hw_info->model); + goto error; + } + + sdi = g_malloc0(sizeof(*sdi)); + sdi->vendor = g_strdup(TEKTRONIX); + sdi->model = g_strdup(hw_info->model); + sdi->version = g_strdup(hw_info->firmware_version); + sdi->serial_num = g_strdup(hw_info->serial_number); + sdi->conn = scpi; + sdi->driver = &tektronix_tds_driver_info; + sdi->inst_type = SR_INST_SCPI; + + devc = g_malloc0(sizeof(*devc)); + g_rec_mutex_init(&devc->mutex); + devc->model = device; + sdi->priv = devc; + + // give us a buffer on our buffer + devc->buffer = g_malloc(TEK_BUFFER_SIZE + 1000); + devc->limit_frames = 1; + devc->capture_mode = CAPTURE_MEMORY; + + sr_scpi_hw_info_free(hw_info); + + devc->analog_groups = g_malloc0( + sizeof(struct sr_channel_group *) * device->channels); + + for (i = 0; i < (unsigned)device->channels; i++) { + channel_name = g_strdup_printf("CH%d", i + 1); + ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, channel_name); + + devc->analog_groups[i] = sr_channel_group_new( + sdi, channel_name, NULL); + devc->analog_groups[i]->channels = g_slist_append(NULL, ch); + } + + return sdi; + +error: + sr_scpi_hw_info_free(hw_info); + g_free(devc); + sr_dev_inst_free(sdi); + + return NULL; +} + +static GSList *scan(struct sr_dev_driver *di, GSList *options) +{ + return sr_scpi_scan(di->context, options, probe_device); +} + +static int dev_open(struct sr_dev_inst *sdi) +{ + int ret; + struct sr_scpi_dev_inst *scpi = sdi->conn; + struct dev_context *devc = sdi->priv; + + if ((ret = sr_scpi_open(scpi)) < 0) { + sr_err("Failed to open SCPI device: %s.", sr_strerror(ret)); + return SR_ERR; + } + + if ((ret = tektronix_tds_get_dev_cfg(sdi)) < 0) { + sr_err("Failed to get device config: %s.", sr_strerror(ret)); + return SR_ERR; + } + + sr_info( + "Opened Tektronix device '%s' with %d channels, %dMHz bandwidth, and %dMSa/s", + devc->model->model, devc->model->channels, devc->model->bandwidth, + devc->model->sample_rate); + + return SR_OK; +} + +static int dev_close(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + + devc = (sdi) ? sdi->priv : NULL; + if (devc) { + g_rec_mutex_clear(&devc->mutex); + g_free(devc->buffer); + } + + return sr_scpi_close(sdi->conn); +} + +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + struct sr_channel *ch; + const char *tmp_str; + int analog_channel = -1; + float smallest_diff = INFINITY; + int idx = -1; + unsigned i; + + if (!sdi) + return SR_ERR_ARG; + + devc = sdi->priv; + + /* If a channel group is specified, it must be a valid one. */ + if (cg && !g_slist_find(sdi->channel_groups, cg)) { + sr_err("Invalid channel group specified."); + return SR_ERR; + } + + if (cg) { + ch = g_slist_nth_data(cg->channels, 0); + if (!ch) + return SR_ERR; + if (ch->type == SR_CHANNEL_ANALOG) { + if (ch->name[2] < '1' || ch->name[2] > '4') + return SR_ERR; + analog_channel = ch->name[2] - '1'; + } + } + + switch (key) { + case SR_CONF_NUM_HDIV: + *data = g_variant_new_int32(TEK_NUM_HDIV); + break; + case SR_CONF_NUM_VDIV: + *data = g_variant_new_int32(TEK_NUM_VDIV); + break; + case SR_CONF_BUFFERSIZE: + *data = g_variant_new_uint64(TEK_BUFFER_SIZE); + break; + case SR_CONF_LIMIT_FRAMES: + *data = g_variant_new_uint64(devc->limit_frames); + break; + case SR_CONF_DATA_SOURCE: + *data = g_variant_new_string(data_sources[devc->capture_mode]); + break; + case SR_CONF_SAMPLERATE: + tektronix_tds_get_dev_cfg_horizontal(sdi); + *data = g_variant_new_uint64(MIN( + TEK_BUFFER_SIZE / (devc->timebase * (float)TEK_NUM_HDIV), + devc->model->sample_rate * 1000000.0)); + break; + case SR_CONF_TRIGGER_SOURCE: + *data = g_variant_new_string(devc->trigger_source); + break; + case SR_CONF_TRIGGER_SLOPE: + if (!g_ascii_strncasecmp(devc->trigger_slope, "RISE", 4)) { + tmp_str = "r"; + } else if (!g_ascii_strncasecmp(devc->trigger_slope, "FALL", 4)) { + tmp_str = "f"; + } else { + sr_dbg("Unknown trigger slope: '%s'.", devc->trigger_slope); + return SR_ERR_NA; + } + *data = g_variant_new_string(tmp_str); + break; + case SR_CONF_TRIGGER_LEVEL: + *data = g_variant_new_double(devc->trigger_level); + break; + case SR_CONF_HORIZ_TRIGGERPOS: + *data = g_variant_new_double(devc->horiz_triggerpos); + break; + case SR_CONF_CAPTURE_RATIO: + *data = g_variant_new_uint64(devc->horiz_triggerpos * 100); + break; + case SR_CONF_TIMEBASE: + for (i = devc->model->timebase_start; + i < ARRAY_SIZE(timebases) - devc->model->timebase_stop; + i++) { + float tb, diff; + + tb = (float)timebases[i][0] / timebases[i][1]; + diff = fabs(devc->timebase - tb); + if (diff < smallest_diff) { + smallest_diff = diff; + idx = i; + } + } + if (idx < devc->model->timebase_start) { + sr_dbg("Negative timebase index: %d.", idx); + return SR_ERR_NA; + } + *data = g_variant_new("(tt)", timebases[idx][0], timebases[idx][1]); + break; + case SR_CONF_VDIV: + if (analog_channel < 0) { + sr_dbg("Negative analog channel: %d.", analog_channel); + return SR_ERR_NA; + } + for (i = devc->model->voltrange_start; + i < ARRAY_SIZE(vdivs) - devc->model->voltrange_stop; i++) { + float vdiv = (float)vdivs[i][0] / vdivs[i][1]; + float diff = fabsf(devc->vdiv[analog_channel] - vdiv); + if (diff < smallest_diff) { + smallest_diff = diff; + idx = i; + } + } + if (idx < devc->model->voltrange_start) { + sr_dbg("Negative vdiv index: %d.", idx); + return SR_ERR_NA; + } + *data = g_variant_new("(tt)", vdivs[idx][0], vdivs[idx][1]); + break; + case SR_CONF_COUPLING: + if (analog_channel < 0) { + sr_dbg("Negative analog channel: %d.", analog_channel); + return SR_ERR_NA; + } + *data = g_variant_new_string(devc->coupling[analog_channel]); + break; + case SR_CONF_PROBE_FACTOR: + if (analog_channel < 0) { + sr_dbg("Negative analog channel: %d.", analog_channel); + return SR_ERR_NA; + } + *data = g_variant_new_uint64(devc->attenuation[analog_channel]); + break; + case SR_CONF_ENABLED: + if (analog_channel < 0) { + sr_dbg("Negative analog channel: %d.", analog_channel); + return SR_ERR_NA; + } + *data = g_variant_new_boolean( + devc->analog_channels[analog_channel]); + break; + case SR_CONF_PEAK_DETECTION: + *data = g_variant_new_boolean(devc->peak_enabled); + break; + case SR_CONF_AVERAGING: + *data = g_variant_new_boolean(devc->average_enabled); + break; + case SR_CONF_AVG_SAMPLES: + *data = g_variant_new_uint64(devc->average_samples); + break; + default: + return SR_ERR_NA; + } + + return SR_OK; +} + +static int config_set(uint32_t key, GVariant *data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + uint64_t p; + double t_dbl; + int i; + int ret, idx; + const char *tmp_str; + char cmd4[4]; + gboolean b; + + devc = sdi->priv; + + /* If a channel group is specified, it must be a valid one. */ + if (cg && !g_slist_find(sdi->channel_groups, cg)) { + sr_err("Invalid channel group specified."); + return SR_ERR; + } + + ret = SR_OK; + switch (key) { + case SR_CONF_LIMIT_FRAMES: + devc->limit_frames = g_variant_get_uint64(data); + sr_info("Getting frames limit of %li", g_variant_get_uint64(data)); + break; + case SR_CONF_TRIGGER_SLOPE: + if ((idx = std_str_idx(data, ARRAY_AND_SIZE(trigger_slopes))) < 0) + return SR_ERR_ARG; + g_free(devc->trigger_slope); + devc->trigger_slope = g_strdup( + (trigger_slopes[idx][0] == 'r') ? "RISE" : "FALL"); + return tektronix_tds_config_set( + sdi, "TRIG:MAI:EDGE:SLO %s", devc->trigger_slope); + case SR_CONF_CAPTURE_RATIO: + t_dbl = g_variant_get_uint64(data) / 100.0; + if (t_dbl < 0.0 || t_dbl > 1.0) { + sr_err("Invalid horiz. trigger position: %g.", t_dbl); + return SR_ERR; + } + /* Fall through */ + case SR_CONF_HORIZ_TRIGGERPOS: + if (key == SR_CONF_HORIZ_TRIGGERPOS) + t_dbl = g_variant_get_double(data); + devc->horiz_triggerpos = t_dbl; + /* We have the trigger offset as a percentage of the frame, but + * need to express this in seconds. */ + t_dbl = -(devc->horiz_triggerpos - 0.5) * devc->timebase * TEK_NUM_HDIV; + return tektronix_tds_config_set(sdi, "hor:mai:pos %.3e", t_dbl); + case SR_CONF_TRIGGER_LEVEL: + if (!strcmp(devc->trigger_source, "AC Line")) + sr_err("Can't set level on AC line trigger, ignoring"); + return SR_ERR; + + t_dbl = g_variant_get_double(data); + ret = tektronix_tds_config_set(sdi, "TRIG:MAI:LEV %.3e", t_dbl); + if (ret == SR_OK) + devc->trigger_level = t_dbl; + break; + case SR_CONF_TIMEBASE: + if ((idx = std_u64_tuple_idx(data, timebases, + ARRAY_SIZE(timebases) - devc->model->timebase_stop)) < 0) + return SR_ERR_ARG; + if (idx < devc->model->timebase_start) + return SR_ERR_ARG; + devc->timebase = (float)timebases[idx][0] / timebases[idx][1]; + ret = tektronix_tds_config_set( + sdi, "hor:sca %.1e", devc->timebase); + if (ret == SR_OK) + tektronix_tds_get_dev_cfg_horizontal(sdi); + return ret; + case SR_CONF_TRIGGER_SOURCE: + if ((idx = std_str_idx(data, devc->model->trigger_sources, + devc->model->num_trigger_sources)) < 0) + return SR_ERR_ARG; + g_free(devc->trigger_source); + devc->trigger_source = g_strdup(devc->model->trigger_sources[idx]); + if (!strcmp(devc->trigger_source, "AC Line")) { + // ONLY set edge trigger, as only edge trigger supports this + // TODO: raise error when not edge source + return tektronix_tds_config_set( + sdi, "TRIG:mai:edge:sou line"); + } else if (!strcmp(devc->trigger_source, "Ext /5")) + tmp_str = "EXT5"; + else if (!strcmp(devc->trigger_source, "Ext /10")) + tmp_str = "EXT10"; + else + tmp_str = (char *)devc->trigger_source; + // Note: pulse and video triggering isn't set here + return tektronix_tds_config_set( + sdi, "TRIG:mai:edge:sou %s", tmp_str); + case SR_CONF_VDIV: + if (!cg) + return SR_ERR_CHANNEL_GROUP; + if ((i = std_cg_idx(cg, devc->analog_groups, + devc->model->channels)) < 0) + return SR_ERR_ARG; + if ((idx = std_u64_tuple_idx(data, ARRAY_AND_SIZE(vdivs))) < 0) + return SR_ERR_ARG; + devc->vdiv[i] = (float)vdivs[idx][0] / vdivs[idx][1]; + ret = tektronix_tds_config_set( + sdi, "CH%d:SCA %.2e", i + 1, (double)devc->vdiv[i]); + return ret; + case SR_CONF_COUPLING: + if (!cg) + return SR_ERR_CHANNEL_GROUP; + if ((i = std_cg_idx(cg, devc->analog_groups, + devc->model->channels)) < 0) + return SR_ERR_ARG; + if ((idx = std_str_idx(data, ARRAY_AND_SIZE(coupling))) < 0) + return SR_ERR_ARG; + g_free(devc->coupling[i]); + devc->coupling[i] = g_strdup(coupling[idx]); + strncpy(cmd4, devc->coupling[i], 3); + cmd4[3] = 0; + return tektronix_tds_config_set( + sdi, "CH%d:COUP %s", i + 1, cmd4); + case SR_CONF_PROBE_FACTOR: + if (!cg) + return SR_ERR_CHANNEL_GROUP; + if ((i = std_cg_idx(cg, devc->analog_groups, + devc->model->channels)) < 0) + return SR_ERR_ARG; + if ((idx = std_u64_idx(data, devc->model->probe_factors, + devc->model->num_probe_factors)) < 0) + return SR_ERR_ARG; + p = g_variant_get_uint64(data); + devc->attenuation[i] = devc->model->probe_factors[idx]; + ret = tektronix_tds_config_set( + sdi, "CH%d:PROBE %" PRIu64, i + 1, p); + if (ret == SR_OK) + tektronix_tds_get_dev_cfg_vertical(sdi); + return ret; + case SR_CONF_ENABLED: + sr_dbg("configuring channel"); + if (!cg) + return SR_ERR_CHANNEL_GROUP; + if ((i = std_cg_idx(cg, devc->analog_groups, + devc->model->channels)) < 0) + return SR_ERR_ARG; + b = g_variant_get_boolean(data); + devc->analog_channels[i] = b; + ret = tektronix_tds_config_set( + sdi, "SEL:CH%d %s", i + 1, b ? "ON" : "OFF"); + return ret; + case SR_CONF_DATA_SOURCE: + if ((idx = std_str_idx(data, ARRAY_AND_SIZE(data_sources))) < 0) + return SR_ERR_ARG; + devc->capture_mode = idx; + break; + case SR_CONF_PEAK_DETECTION: + // TODO: you can configure peak detect mode, but the data isn't + // parsed yet + devc->peak_enabled = g_variant_get_boolean(data); + if (devc->peak_enabled) + ret = tektronix_tds_config_set(sdi, "acq:mode peak"); + else + ret = tektronix_tds_config_set(sdi, "acq:mode sam"); + devc->average_enabled = FALSE; + sr_dbg("%s peak detect", + devc->peak_enabled ? "Enabling" : "Disabling"); + break; + case SR_CONF_AVERAGING: + devc->average_enabled = g_variant_get_boolean(data); + if (devc->average_enabled) + ret = tektronix_tds_config_set(sdi, "acq:mode ave"); + else + ret = tektronix_tds_config_set(sdi, "acq:mode sam"); + devc->peak_enabled = FALSE; + sr_dbg("%s averaging", + devc->average_enabled ? "Enabling" : "Disabling"); + break; + case SR_CONF_AVG_SAMPLES: + devc->average_samples = g_variant_get_uint64(data); + sr_dbg("Setting averaging rate to %d", devc->average_samples); + ret = tektronix_tds_config_set( + sdi, "acq:numav %d", devc->average_samples); + break; + default: + return SR_ERR_NA; + } + + return ret; +} + +static int config_list(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + + devc = (sdi) ? sdi->priv : NULL; + + switch (key) { + case SR_CONF_SCAN_OPTIONS: + case SR_CONF_DEVICE_OPTIONS: + if (!cg) + return STD_CONFIG_LIST( + key, data, sdi, cg, scanopts, drvopts, devopts); + if (!devc) + return SR_ERR_ARG; + if (std_cg_idx(cg, devc->analog_groups, devc->model->channels) < 0) + return SR_ERR_ARG; + *data = std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg_analog)); + return SR_OK; + break; + case SR_CONF_COUPLING: + if (!cg) + return SR_ERR_CHANNEL_GROUP; + *data = g_variant_new_strv(ARRAY_AND_SIZE(coupling)); + break; + case SR_CONF_PROBE_FACTOR: + if (!cg) + return SR_ERR_CHANNEL_GROUP; + *data = std_gvar_array_u64(devc->model->probe_factors, + devc->model->num_probe_factors); + break; + case SR_CONF_VDIV: + if (!devc) + /* Can't know this until we have the exact model. */ + return SR_ERR_ARG; + if (!cg) + return SR_ERR_CHANNEL_GROUP; + *data = std_gvar_tuple_array(ARRAY_AND_SIZE(vdivs)); + break; + case SR_CONF_TIMEBASE: + if (!devc) + /* Can't know this until we have the exact model. */ + return SR_ERR_ARG; + *data = std_gvar_tuple_array( + &timebases[devc->model->timebase_start], + ARRAY_SIZE(timebases) - devc->model->timebase_start - + devc->model->timebase_stop); + break; + case SR_CONF_TRIGGER_SOURCE: + if (!devc) + /* Can't know this until we have the exact model. */ + return SR_ERR_ARG; + *data = g_variant_new_strv(devc->model->trigger_sources, + devc->model->num_trigger_sources); + break; + case SR_CONF_TRIGGER_SLOPE: + *data = g_variant_new_strv(ARRAY_AND_SIZE(trigger_slopes)); + break; + case SR_CONF_DATA_SOURCE: + *data = g_variant_new_strv(ARRAY_AND_SIZE(data_sources)); + break; + case SR_CONF_NUM_HDIV: + *data = g_variant_new_int32(TEK_NUM_HDIV); + break; + case SR_CONF_NUM_VDIV: + *data = g_variant_new_int32(TEK_NUM_VDIV); + break; + case SR_CONF_AVG_SAMPLES: + *data = std_gvar_array_u64(ARRAY_AND_SIZE(averages)); + break; + default: + return SR_ERR_NA; + } + + return SR_OK; +} + +static int dev_acquisition_start(const struct sr_dev_inst *sdi) +{ + struct sr_scpi_dev_inst *scpi; + struct dev_context *devc; + struct sr_channel *ch; + GSList *l; + char *response = NULL; + + scpi = sdi->conn; + devc = sdi->priv; + + devc->num_frames = 0; + + for (l = sdi->channels; l; l = l->next) { + ch = l->data; + if (ch->enabled) + devc->enabled_channels = g_slist_append( + devc->enabled_channels, ch); + if (ch->enabled != devc->analog_channels[ch->index]) { + /* Enabled channel is currently disabled, or vice versa. */ + if (tektronix_tds_config_set(sdi, "SEL:CH%d %s", + ch->index + 1, + ch->enabled ? "ON" : "OFF") != SR_OK) + return SR_ERR; + devc->analog_channels[ch->index] = ch->enabled; + } + } + if (!devc->enabled_channels) + return SR_ERR; + + g_rec_mutex_lock(&devc->mutex); + + // Set view to main, but don't check the status as TBS1000B doesn't + // support this command. This ensures the timebase is correct + tektronix_tds_config_set(sdi, "hor:view mai"); + + tektronix_tds_get_dev_cfg_horizontal(sdi); + + if (sr_scpi_get_bool(scpi, "acq:state?", &devc->prior_state_running) != SR_OK) + return SR_ERR; + + if (sr_scpi_get_string(scpi, "acq:stopa?", &response) != SR_OK) + return SR_ERR; + + devc->prior_state_single = + (g_ascii_strncasecmp("sequence", response, 3) == 0); + + // these models are slow, and TDS2xxxB takes ~1.5 seconds to begin + // transmitting, so poll slowly However, not too slow, as that makes + // some weird transmission timeouts appear + sr_scpi_source_add(sdi->session, scpi, G_IO_IN, 500, + tektronix_tds_receive, (void *)sdi); + + std_session_send_df_header(sdi); + + devc->channel_entry = devc->enabled_channels; + + if (tektronix_tds_capture_start(sdi) != SR_OK) { + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; + } + + /* Start of first frame. */ + std_session_send_df_frame_begin(sdi); + + return SR_OK; +} + +static int dev_acquisition_stop(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_scpi_dev_inst *scpi; + + devc = sdi->priv; + + std_session_send_df_end(sdi); + + g_slist_free(devc->enabled_channels); + devc->enabled_channels = NULL; + scpi = sdi->conn; + sr_scpi_source_remove(sdi->session, scpi); + + return SR_OK; +} + +static struct sr_dev_driver tektronix_tds_driver_info = { + // Awkward name, but there isn't a proper name for it + .name = "tektronix-tds", + .longname = "Tektronix TDS/TBS/TPS", + .api_version = 1, + .init = std_init, + .cleanup = std_cleanup, + .scan = scan, + .dev_list = std_dev_list, + .dev_clear = std_dev_clear, + .config_get = config_get, + .config_set = config_set, + .config_list = config_list, + .dev_open = dev_open, + .dev_close = dev_close, + .dev_acquisition_start = dev_acquisition_start, + .dev_acquisition_stop = dev_acquisition_stop, + .context = NULL, +}; +SR_REGISTER_DEV_DRIVER(tektronix_tds_driver_info); diff --git a/src/hardware/tektronix-tds/protocol.c b/src/hardware/tektronix-tds/protocol.c new file mode 100644 index 000000000..f1cd15d9e --- /dev/null +++ b/src/hardware/tektronix-tds/protocol.c @@ -0,0 +1,814 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Patrick Plenefisch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "scpi.h" +#include "protocol.h" + +struct tek_enum_parser { + int enum_value; + const char *name; +}; + +static const struct tek_enum_parser parse_table_data_encoding[] = { + {ENC_ASCII, "ASC"}, {ENC_BINARY, "BIN"}, {0, NULL}}; +static const struct tek_enum_parser parse_table_data_format[] = { + {FMT_RI, "RI"}, {FMT_RP, "RP"}, {0, NULL}}; +static const struct tek_enum_parser parse_table_data_ordering[] = { + {ORDER_LSB, "LSB"}, {ORDER_MSB, "MSB"}, {0, NULL}}; +static const struct tek_enum_parser parse_table_point_format[] = { + {PT_FMT_ENV, "ENV"}, {PT_FMT_Y, "Y"}, {0, NULL}}; +static const struct tek_enum_parser parse_table_xunits[] = { + {XU_SECOND, "s"}, {XU_HZ, "Hz"}, {0, NULL}}; +static const struct tek_enum_parser parse_table_yunits[] = {{YU_UNKNOWN, "U"}, + {YU_UNKNOWN_MASK, "?"}, {YU_VOLTS, "Volts"}, {YU_DECIBELS, "dB"}, + + // select models only: + {YU_AMPS, "A"}, {YU_AA, "AA"}, {YU_VA, "VA"}, {YU_VV, "VV"}, {0, NULL}}; + +#define TEK_PRE_HEADER_FIELDS 16 + +static int parse_scpi_int(const char *data, int *out_err, int default_value) +{ + int value = default_value; + struct sr_rational ret_rational; + int ret; + + ret = sr_parse_rational(data, &ret_rational); + if (ret == SR_OK && (ret_rational.p % ret_rational.q) == 0) { + value = ret_rational.p / ret_rational.q; + } else { + sr_dbg("get_int: non-integer rational=%" PRId64 "/%" PRIu64, + ret_rational.p, ret_rational.q); + *out_err = SR_ERR_DATA; + } + + return value; +} + +static float parse_scpi_float(const char *data, int *out_err, float default_value) +{ + float value = default_value; + + if (sr_atof_ascii(data, &value) != SR_OK) + *out_err = SR_ERR_DATA; + + return value; +} + +static const char *parse_scpi_string(char *data, int *out_err) +{ + (void)out_err; + return sr_scpi_unquote_string(data); +} + +static int parse_scpi_enum(const char *data, + const struct tek_enum_parser *parser_table, int *out_err, int default_value) +{ + while (parser_table->name) { + if (g_ascii_strcasecmp(parser_table->name, data) == 0) { + return parser_table->enum_value; + } + parser_table++; + } + *out_err = SR_ERR_DATA; + return default_value; +} + +static const char *render_scpi_enum( + int value, const struct tek_enum_parser *parser_table, int *out_err) +{ + while (parser_table->name) { + if (value == parser_table->enum_value) { + return parser_table->name; + } + parser_table++; + } + *out_err = SR_ERR_DATA; + return "NULL"; +} + +static void check_expected_value( + const char* name, int actual, int expected, int* out_err, + const struct tek_enum_parser *parser_table) +{ + if (actual != expected) { + *out_err = SR_ERR_DATA; + if (parser_table == NULL) + sr_err( + "Error validating data header. Field '%s' expected %d, but found %d", + name, expected, actual); + else { + sr_err( + "Error validating data header. Field '%s' expected %s, but found %s", + name, + render_scpi_enum(expected, parser_table, out_err), + render_scpi_enum(actual, parser_table, out_err)); + } + } +} + +static int tektronix_tds_parse_header(struct sr_dev_inst *sdi, GString *buffer) +{ + struct sr_scpi_dev_inst *scpi = sdi->conn; + struct dev_context *devc = sdi->priv; + gchar *buf = buffer->str; + gchar *end_buf = &buffer->str[buffer->len]; + char *fields[TEK_PRE_HEADER_FIELDS + 1]; // one extra for block + int i = 0; + int ret = SR_OK; + int pt_off; + const char *wfid; + int bit_width; + int byte_width; + enum TEK_POINT_FORMAT pt_format; + enum TEK_DATA_ORDERING ordering; + enum TEK_DATA_FORMAT format; + enum TEK_DATA_ENCODING encoding; + (void)scpi; + + sr_dbg("Parsing header of size %d", (int)(end_buf - buf)); + sr_spew("Line as receved: %.*s", (int)(end_buf - buf - 1), buf); + + // Parse in 3 steps: + // 1. find all semicolons, and replace with null bytes + // 2. Put next char reference into array + // 3. Parse each type based on array index + fields[i++] = buf; + while (buf < end_buf) { + if (*buf == ';') { + *buf = 0; // turn into list of C strings + fields[i++] = buf + 1; + } + buf++; + } + sr_spew("Expected 17 indexes, found %d in header", i); + /* + BYT_Nr ; + BIT_Nr ; + ENCdg { ASC | BIN }; + BN_Fmt { RI | RP }; + BYT_Or { LSB | MSB }; + NR_Pt ; + WFID ; + PT_FMT {ENV | Y}; + XINcr ; + PT_Off ; + XZERo ; + XUNit; + YMUlt ; + YZEro ; + YOFF ; + YUNit ; + #..block + */ + + i = 0; + byte_width = parse_scpi_int(fields[i++], &ret, 1); + bit_width = parse_scpi_int(fields[i++], &ret, 8); + encoding = parse_scpi_enum( + fields[i++], parse_table_data_encoding, &ret, ENC_ASCII); + format = parse_scpi_enum( + fields[i++], parse_table_data_format, &ret, FMT_RI); + ordering = parse_scpi_enum( + fields[i++], parse_table_data_ordering, &ret, ORDER_LSB); + devc->wavepre.num_pts = parse_scpi_int(fields[i++], &ret, -1); + wfid = parse_scpi_string(fields[i++], &ret); + pt_format = parse_scpi_enum( + fields[i++], parse_table_point_format, &ret, PT_FMT_Y); + devc->wavepre.x_incr = parse_scpi_float(fields[i++], &ret, 1); + pt_off = parse_scpi_int(fields[i++], &ret, 0); + devc->wavepre.x_zero = parse_scpi_float(fields[i++], &ret, 0); + devc->wavepre.x_unit = parse_scpi_enum(sr_scpi_unquote_string(fields[i++]), + parse_table_xunits, &ret, XU_SECOND); + devc->wavepre.y_mult = parse_scpi_float(fields[i++], &ret, 0); + devc->wavepre.y_zero = parse_scpi_float(fields[i++], &ret, 0); + devc->wavepre.y_off = parse_scpi_float(fields[i++], &ret, 0); + devc->wavepre.y_unit = parse_scpi_enum(sr_scpi_unquote_string(fields[i++]), + parse_table_yunits, &ret, YU_UNKNOWN); + + sr_dbg("Expected 16 values, parsed %d in header with ret=%i", i, ret); + if (i != TEK_PRE_HEADER_FIELDS && ret == SR_OK) + ret = SR_ERR; + + // expensive, so avoid + if (sr_log_loglevel_get() >= SR_LOG_SPEW) + sr_spew("Line is parsed as: %d;%d;%s;%s;%s;%i;\"%s\";%s;%.2e;%i;%.2e;\"%s\";%.2e;%.2e;%.2e;\"%s\";%c... ", + byte_width, bit_width, + render_scpi_enum(encoding, parse_table_data_encoding, &ret), + render_scpi_enum(format, parse_table_data_format, &ret), + render_scpi_enum(ordering, parse_table_data_ordering, &ret), + devc->wavepre.num_pts, wfid, + render_scpi_enum(pt_format, parse_table_point_format, &ret), + devc->wavepre.x_incr, pt_off, devc->wavepre.x_zero, + render_scpi_enum( + devc->wavepre.x_unit, parse_table_xunits, &ret), + devc->wavepre.y_mult, devc->wavepre.y_zero, + devc->wavepre.y_off, + render_scpi_enum(devc->wavepre.y_unit, + parse_table_yunits, &ret), + '#' + ); + + // check that settings weren't tampered with + check_expected_value("byte width", byte_width, 1, &ret, NULL); + check_expected_value("bit size", bit_width, 8, &ret, NULL); + check_expected_value("data encoding", encoding, ENC_BINARY, &ret, parse_table_data_encoding); + check_expected_value("data format", format, FMT_RI, &ret, parse_table_data_format); + check_expected_value("data encoding", ordering, ORDER_MSB, &ret, parse_table_data_ordering); + check_expected_value("number of points", devc->wavepre.num_pts, TEK_BUFFER_SIZE, &ret, NULL); + // this value is ENV when in peak detect mode + check_expected_value("point format", pt_format, PT_FMT_Y, &ret, parse_table_point_format); + + check_expected_value("point offset", pt_off, 0, &ret, NULL); + return ret; +} + +/* Find all 16 fields by locating their semicolons. + * In theory the string values could contain semicolons to throw us off + * but I think we are safe based on the docs + */ +static int tektronix_tds_count_headers(void *cb_data, + const char *rsp_data, size_t rsp_dlen, size_t *block_off) +{ + const char *rsp_start = rsp_data; + size_t found_fields = 0; + + (void)cb_data; + + /* Zero input length is acceptable here. */ + if (rsp_data == NULL || block_off == NULL) + return SR_ERR_ARG; + + while (rsp_dlen--> 0) { + if (*rsp_data++ == ';') { + found_fields++; + + // if we found all fields, return the end of the header + if (found_fields == TEK_PRE_HEADER_FIELDS) { + // found all fields, this is the start of the block + *block_off = rsp_data - rsp_start; + return SR_OK; + } + } + } + + return 1; /* Need more receive data. */ +} + + +/* Read the header and prepare the data of a data block. */ +static int tektronix_tds_read_header_and_data(struct sr_dev_inst *sdi) +{ + struct sr_scpi_dev_inst *scpi = sdi->conn; + struct dev_context *devc = sdi->priv; + char *buf = (char *)devc->buffer; + int ret; + GByteArray *block = NULL; + GString *text; + + ret = sr_scpi_get_text_then_block(scpi, NULL, + tektronix_tds_count_headers, NULL, &text, &block); + + + if (ret != SR_OK) { + sr_err("Read error while reading data: %d", + ret); + ret = 1; // hack to avoid loosing data. We didn't start reading yet + goto err; + } + + // header is variable, but at least 100 bytes, and likely no more than + // 175 bytes. Typical values are around 150 + + if (tektronix_tds_parse_header(sdi, text) != SR_OK) { + goto err; + } + + // copy points to our buffer + if (block->len != TEK_BUFFER_SIZE) { + sr_err("Expected full buffer, got one of size %d instead", + block->len); + ret = SR_ERR; + goto err; + } + memcpy(buf, block->data, block->len); + devc->num_block_read = block->len; + + g_byte_array_free(block, TRUE); + g_string_free(text, TRUE); + + return SR_OK; +err: + if (block != NULL) + g_byte_array_free(block, TRUE); + if (text != NULL) + g_string_free(text, TRUE); + return ret; +} + +/* Send a configuration setting. */ +SR_PRIV int tektronix_tds_config_set( + const struct sr_dev_inst *sdi, const char *format, ...) +{ + va_list args; + int ret; + struct dev_context *devc; + + devc = sdi->priv; + + g_rec_mutex_lock(&devc->mutex); + + va_start(args, format); + ret = sr_scpi_send_variadic(sdi->conn, format, args); + va_end(args); + + g_rec_mutex_unlock(&devc->mutex); + + return ret; +} + +SR_PRIV int tektronix_tds_get_dev_cfg_horizontal(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + float fvalue; + int memory_depth; + + devc = sdi->priv; + g_rec_mutex_lock(&devc->mutex); + + /* Get the timebase. */ + if (sr_scpi_get_float(sdi->conn, "hor:sca?", &devc->timebase) != SR_OK) + goto err; + sr_dbg("Current timebase: %g.", devc->timebase); + + /* Get the record size. A sanity check as it should be 2500 */ + if (sr_scpi_get_int(sdi->conn, "hor:reco?", &memory_depth) != SR_OK) + goto err; + + if (memory_depth != TEK_BUFFER_SIZE) { + sr_err("A Tek 2k5 device should have that much memory. Expecting: 2500 bytes, found %d bytes", + memory_depth); + goto err; + } + + fvalue = TEK_BUFFER_SIZE / (devc->timebase * (float)TEK_NUM_HDIV); + if (devc->model->sample_rate * 1000000.0 < fvalue) + sr_dbg("Current samplerate: %i MSa/s (limited by device).", + devc->model->sample_rate); + else + sr_dbg("Current samplerate: %ld Sa/s.", (long)fvalue); + + // TODO: peak detect mode is half of this + sr_dbg("Current memory depth: %d.", TEK_BUFFER_SIZE); + g_rec_mutex_unlock(&devc->mutex); + return SR_OK; +err: + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; +} + + +SR_PRIV int tektronix_tds_get_dev_cfg_vertical(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + char *cmd; + int i; + int res; + + devc = sdi->priv; + g_rec_mutex_lock(&devc->mutex); + + /* Vertical gain. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("CH%d:SCA?", i + 1); + res = sr_scpi_get_float(sdi->conn, cmd, &devc->vdiv[i]); + g_free(cmd); + if (res != SR_OK) + goto err; + } + sr_dbg("Current vertical gain:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %g", i + 1, devc->vdiv[i]); + + /* Vertical offset. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("CH%d:POS?", i + 1); + res = sr_scpi_get_float(sdi->conn, cmd, &devc->vert_offset[i]); + g_free(cmd); + if (res != SR_OK) + goto err; + } + sr_dbg("Current vertical offset:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %g", i + 1, devc->vert_offset[i]); + + g_rec_mutex_unlock(&devc->mutex); + return SR_OK; +err: + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; +} + +SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_channel *ch; + char *cmd, *response; + int i; + int res; + + devc = sdi->priv; + g_rec_mutex_lock(&devc->mutex); + + /* Analog channel state. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("SELECT:CH%i?", i + 1); + res = sr_scpi_get_bool(sdi->conn, cmd, &devc->analog_channels[i]); + g_free(cmd); + if (res != SR_OK) + goto err; + ch = g_slist_nth_data(sdi->channels, i); + ch->enabled = devc->analog_channels[i]; + } + sr_dbg("Current analog channel state:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %s", i + 1, devc->analog_channels[i] ? "On" : "Off"); + + /* Probe attenuation. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("CH%d:PROBE?", i + 1); + res = sr_scpi_get_float(sdi->conn, cmd, &devc->attenuation[i]); + g_free(cmd); + if (res != SR_OK) + goto err; + } + sr_dbg("Current probe attenuation:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %g", i + 1, devc->attenuation[i]); + + /* Vertical gain and offset. */ + if (tektronix_tds_get_dev_cfg_vertical(sdi) != SR_OK) + goto err; + + if (tektronix_tds_get_dev_cfg_horizontal(sdi) != SR_OK) + goto err; + + /* Coupling. */ + for (i = 0; i < devc->model->channels; i++) { + cmd = g_strdup_printf("CH%d:COUP?", i + 1); + g_free(devc->coupling[i]); + devc->coupling[i] = NULL; + res = sr_scpi_get_string(sdi->conn, cmd, &devc->coupling[i]); + g_free(cmd); + if (res != SR_OK) + goto err; + } + + sr_dbg("Current coupling:"); + for (i = 0; i < devc->model->channels; i++) + sr_dbg("CH%d %s", i + 1, devc->coupling[i]); + + /* Trigger source. edge, pulse, and video are always the same, it + * appears */ + response = NULL; + g_free(devc->trigger_source); + if (sr_scpi_get_string(sdi->conn, "TRIG:MAI:edge:sou?", + &devc->trigger_source) != SR_OK) + goto err; + sr_dbg("Current trigger source: %s.", devc->trigger_source); + + /* Horizontal trigger position. */ + if (sr_scpi_get_float(sdi->conn, "hor:pos?", &devc->horiz_triggerpos) != + SR_OK) + goto err; + + // triggerpos is in timeunits, convert back to percentage + devc->horiz_triggerpos = + (-devc->horiz_triggerpos / (devc->timebase * 10)) + 0.5; + + sr_dbg("Current horizontal trigger position %.10f.", + devc->horiz_triggerpos); + + /* Trigger slope. */ + g_free(devc->trigger_slope); + devc->trigger_slope = NULL; + res = sr_scpi_get_string( + sdi->conn, "trig:mai:edge:slope?", &devc->trigger_slope); + if (res != SR_OK) + goto err; + sr_dbg("Current trigger slope: %s.", devc->trigger_slope); + + /* Trigger level. */ + res = sr_scpi_get_float(sdi->conn, "trig:mai:lev?", &devc->trigger_level); + if (res != SR_OK) + goto err; + sr_dbg("Current trigger level: %g.", devc->trigger_level); + + /* Averaging/peak detection */ + response = NULL; + if (sr_scpi_get_string(sdi->conn, "acq:mod?", &response) != SR_OK) + goto err; + devc->average_enabled = g_ascii_strncasecmp(response, "average", 3) == 0; + devc->peak_enabled = g_ascii_strncasecmp(response, "peak", 3) == 0; + sr_dbg("Acquisition mode: %s.", response); + g_free(response); + + if (sr_scpi_get_int(sdi->conn, "acq:numav?", &devc->average_samples) != SR_OK) + goto err; + sr_dbg("Averaging samples: %i.", devc->average_samples); + + g_rec_mutex_unlock(&devc->mutex); + return SR_OK; +err: + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; +} + +/* Revert all settings, if requested. */ +SR_PRIV int tektronix_tds_capture_finish(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + + if (!(devc = sdi->priv)) + return SR_ERR; + + devc->acquire_status = WAIT_DONE; + + sr_dbg("Setting exiting setttings back"); + + if (devc->capture_mode == CAPTURE_LIVE || + devc->capture_mode == CAPTURE_DISPLAY) { + if (tektronix_tds_config_set(sdi, "ACQ:stopa runstop") != SR_OK) + goto err; + + if (tektronix_tds_config_set(sdi, "ACQ:STATE RUN") != SR_OK) + goto err; + } + + g_rec_mutex_unlock(&devc->mutex); + return SR_OK; +err: + g_rec_mutex_unlock(&devc->mutex); + return SR_ERR; +} + +/* Start reading data from the current channel. */ +SR_PRIV int tektronix_tds_channel_start(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_channel *ch; + + if (!(devc = sdi->priv)) + return SR_ERR; + + ch = devc->channel_entry->data; + + sr_dbg("Configure reading data from channel %s.", ch->name); + + if (sr_scpi_send(sdi->conn, "DAT:SOU CH%d", ch->index + 1) != SR_OK) + return SR_ERR; + + // wait for trigger (asynchronous) + if (devc->acquire_status == WAIT_CAPTURE && + (devc->num_frames > 0 || devc->capture_mode == CAPTURE_LIVE || + devc->capture_mode == CAPTURE_ONE_SHOT || + devc->prior_state_running)) + if (sr_scpi_send(sdi->conn, "*WAI") != SR_OK) + return SR_ERR; + devc->acquire_status = WAIT_CHANNEL; + + sr_dbg("Requesting waveform"); + if (sr_scpi_send(sdi->conn, "WAVF?") != SR_OK) + return SR_ERR; + + devc->num_block_read = 0; + + return SR_OK; +} + +/* Start capturing a new frameset. */ +SR_PRIV int tektronix_tds_capture_start(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + + if (!(devc = sdi->priv)) + return SR_ERR; + + // Force our capture settings to 1 byte, msb, binary + if (tektronix_tds_config_set(sdi, "dat:enc RIB") != SR_OK) + return SR_ERR; + if (tektronix_tds_config_set(sdi, "dat:wid 1") != SR_OK) + return SR_ERR; + + devc->acquire_status = WAIT_CAPTURE; + + if (devc->num_frames == 0) { + // if we aren't requesting memory, create a new capture + // if we are requesting memory, but it was already running, + // convert to single-shot so we can synchronize channels + if (devc->capture_mode == CAPTURE_LIVE || + devc->capture_mode == CAPTURE_ONE_SHOT || + devc->prior_state_running) { + sr_dbg("Triggering restart"); + // stop before setting single sequence mode, so that we + // can get the same waveform data per channel + if (!devc->prior_state_single) { + if (tektronix_tds_config_set( + sdi, "ACQ:STATE STOP") != SR_OK) + return SR_ERR; + if (tektronix_tds_config_set( + sdi, "ACQ:stopa seq") != SR_OK) + return SR_ERR; + } + if (tektronix_tds_config_set(sdi, "ACQ:STATE RUN") != + SR_OK) + return SR_ERR; + } + } else { + // If you are requesting multiple frames, all capture modes reset + if (tektronix_tds_config_set(sdi, "ACQ:STATE RUN") != SR_OK) + return SR_ERR; + } + + if (tektronix_tds_channel_start(sdi) != SR_OK) + return SR_ERR; + + sr_dbg("Starting data capture for curves."); + + return SR_OK; +} + +SR_PRIV int tektronix_tds_receive(int fd, int revents, void *cb_data) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + + struct sr_scpi_dev_inst *scpi; + struct sr_datafeed_packet packet; + struct sr_datafeed_analog analog; + struct sr_analog_encoding encoding; + struct sr_analog_meaning meaning; + struct sr_analog_spec spec; + struct sr_channel *ch; + int len, i; + + (void)fd; + + sdi = cb_data; + if (!sdi) + return TRUE; + + devc = sdi->priv; + if (!devc) + return TRUE; + scpi = sdi->conn; + ch = devc->channel_entry->data; + + if (revents == G_IO_IN || TRUE) { // this is always 0 for some reason + + // no data yet + sr_dbg("Waiting for data..."); + len = tektronix_tds_read_header_and_data(sdi); + if (len == 1) + /* Still reading the header. */ + return TRUE; + + sr_dbg("New block with header expected."); + + if (len == -1) { + sr_err("Read error, aborting capture."); + std_session_send_df_frame_end(sdi); + sdi->driver->dev_acquisition_stop(sdi); + return TRUE; + } + + devc->acquire_status = WAIT_DONE; + + // streaming data back is pretty fast, at least once the scope + // eventually starts sending it our way + + sr_dbg("Transfer has been completed."); + if (!sr_scpi_read_complete(scpi)) { + sr_err("Read should have been completed."); + std_session_send_df_frame_end(sdi); + sdi->driver->dev_acquisition_stop(sdi); + return TRUE; + } + + // We have received the entire 2.5k buffer now, so process it, + // ignoring the trailing \n + len = TEK_BUFFER_SIZE; + + float vdiv = devc->vdiv[ch->index]; + GArray *float_data; + static GArray *data; + float voltage, vdivlog; + int digits; + + data = g_array_sized_new(FALSE, FALSE, sizeof(uint8_t), len); + g_array_append_vals(data, devc->buffer, len); + float_data = g_array_new(FALSE, FALSE, sizeof(float)); + for (i = 0; i < len; i++) { + voltage = (float)g_array_index(data, int8_t, i) - + devc->wavepre.y_off; + voltage = ((devc->wavepre.y_mult * voltage) + + devc->wavepre.y_zero); + g_array_append_val(float_data, voltage); + } + vdivlog = log10f(vdiv); + digits = -(int)vdivlog + (vdivlog < 0.0) + 3 /* 8-bit resolution*/ - 1; + sr_analog_init(&analog, &encoding, &meaning, &spec, digits); + analog.meaning->channels = g_slist_append(NULL, ch); + analog.num_samples = float_data->len; + analog.data = ((float *)float_data->data); + if (devc->wavepre.y_unit == YU_VOLTS) { + analog.meaning->mq = SR_MQ_VOLTAGE; + analog.meaning->unit = SR_UNIT_VOLT; + } else if (devc->wavepre.y_unit == YU_AMPS) { + analog.meaning->mq = SR_MQ_CURRENT; + analog.meaning->unit = SR_UNIT_AMPERE; + } else if (devc->wavepre.y_unit == YU_DECIBELS) { + analog.meaning->mq = SR_MQ_POWER; + analog.meaning->unit = SR_UNIT_DECIBEL_MW; + } else { + analog.meaning->mq = 0; + analog.meaning->unit = SR_UNIT_UNITLESS; + } + analog.meaning->mqflags = 0; + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + sr_dbg("Computing using trigger point %.6f", devc->horiz_triggerpos); + // only the first packet provides trigger information, all others + // are "after" the correct timebase + if (devc->channel_entry != devc->enabled_channels) { + sr_session_send(sdi, &packet); + } else if (devc->horiz_triggerpos > 0) { + // This will round to (potentially) twice the expected margin + // on-device (% -> s -> %) vs our expectation (%) + analog.num_samples = float_data->len * devc->horiz_triggerpos; + sr_dbg("First batch has %d", analog.num_samples); + sr_session_send(sdi, &packet); + std_session_send_df_trigger(sdi); + if (devc->horiz_triggerpos < 1) { + analog.data = ((float *)float_data->data) + analog.num_samples; + analog.num_samples = float_data->len - analog.num_samples; + sr_dbg("second batch has %d", analog.num_samples); + sr_session_send(sdi, &packet); + } + } else { // trigger == 0 + std_session_send_df_trigger(sdi); + sr_session_send(sdi, &packet); + } + g_slist_free(analog.meaning->channels); + g_array_free(data, TRUE); + + if (devc->channel_entry->next) { + sr_dbg("Doing another channel"); + /* We got the frame for this channel, now get the next channel. */ + devc->channel_entry = devc->channel_entry->next; + tektronix_tds_channel_start(sdi); + } else { + /* Done with this frame. */ + std_session_send_df_frame_end(sdi); + if (++devc->num_frames == devc->limit_frames) { + /* Last frame, stop capture. */ + sdi->driver->dev_acquisition_stop(sdi); + tektronix_tds_capture_finish(sdi); + } else { + sr_dbg("Doing another frame"); + /* Get the next frame, starting with the first channel. */ + devc->channel_entry = devc->enabled_channels; + tektronix_tds_capture_start(sdi); + + /* Start of next frame. */ + std_session_send_df_frame_begin(sdi); + } + } + } + return TRUE; +} diff --git a/src/hardware/tektronix-tds/protocol.h b/src/hardware/tektronix-tds/protocol.h new file mode 100644 index 000000000..98e66a51d --- /dev/null +++ b/src/hardware/tektronix-tds/protocol.h @@ -0,0 +1,213 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2023 Patrick Plenefisch + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBSIGROK_HARDWARE_TEKTRONIX_TDS_PROTOCOL_H +#define LIBSIGROK_HARDWARE_TEKTRONIX_TDS_PROTOCOL_H + +#include +#include +#include +#include "libsigrok-internal.h" + +#define LOG_PREFIX "tektronix-tds" + +/* Mostly for general information, but also used for debug messages */ +enum bandwidth +{ + BW_25MHz = 25, + BW_30MHz = 30, + BW_40MHz = 40, + BW_45MHz = 45, + BW_50MHz = 50, + BW_60MHz = 60, + BW_70MHz = 70, + BW_100MHz = 100, + BW_150MHz = 150, + BW_200MHz = 200, +}; + +enum samplerate +{ + SA_500M = 500, + SA_1G = 1000, + SA_2G = 2000, +}; + +/* Describes model-specific features. See DEVICE_SPEC() macro in api.c for the + * "constructor" */ +struct device_spec { + const char *model; + int channels; + + enum samplerate sample_rate; + enum bandwidth bandwidth; + + const uint64_t *probe_factors; + int num_probe_factors; + + int timebase_start; + int timebase_stop; + + int voltrange_start; + int voltrange_stop; + + const char **trigger_sources; + int num_trigger_sources; +}; + +/* Values that are the same for all models */ +#define TEK_BUFFER_SIZE 2500 +// all scopes have -5 to +5 hdivs +// and -4 to +4 vdivs +#define TEK_NUM_HDIV 10 +#define TEK_NUM_VDIV 8 +#define MAX_ANALOG_CHANNELS 4 + +/* Wave data information */ + +enum TEK_DATA_ENCODING +{ + ENC_ASCII, + ENC_BINARY +}; + +enum TEK_DATA_FORMAT +{ + FMT_RI, + FMT_RP +}; + +enum TEK_DATA_ORDERING +{ + ORDER_LSB, + ORDER_MSB +}; + +enum TEK_POINT_FORMAT +{ + PT_FMT_ENV, + PT_FMT_Y +}; + +enum TEK_X_UNITS +{ + XU_SECOND, + XU_HZ +}; + +enum TEK_Y_UNITS +{ + YU_UNKNOWN, + YU_UNKNOWN_MASK, + YU_VOLTS, + YU_DECIBELS, + + // TBS1000B/EDU, TBS1000, TDS2000C, TDS1000C-EDU, TDS2000B, + // TDS1000B, TPS2000B, and TPS2000 Series only: + YU_AMPS, + YU_VV, + YU_VA, + YU_AA +}; + +struct most_recent_wave_preamble { + // Xn = XZEro + XINcr (n - PT_OFf) + float x_zero; // (in xunis) + float x_incr; // seconds per point or herts per point + enum TEK_X_UNITS x_unit; + + // value_in_YUNits = ((curve_in_dl - YOFF_in_dl) * YMUlt) + + // YZERO_in_YUNits + float y_mult; // (in yunits) + float y_off; // (in digitizer levels) + float y_zero; // (in yunits) + enum TEK_Y_UNITS y_unit; + + int num_pts; +}; + +enum DRIVER_CAPTURE_MODE +{ + CAPTURE_LIVE, // reset trigger, re-enable at end + CAPTURE_ONE_SHOT, // reset trigger, no clear + CAPTURE_DISPLAY, // no reset, re-enable at end + CAPTURE_MEMORY, // no reset, no clear +}; + +enum wait_events +{ + WAIT_CAPTURE, + WAIT_CHANNEL, + WAIT_DONE, +}; + +struct dev_context { + /* Core information */ + struct sr_channel_group **analog_groups; + const struct device_spec *model; + GRecMutex mutex; + + /* Current & configured channel settings */ + gboolean analog_channels[MAX_ANALOG_CHANNELS]; + float vdiv[MAX_ANALOG_CHANNELS]; + float vert_offset[MAX_ANALOG_CHANNELS]; + float attenuation[MAX_ANALOG_CHANNELS]; + char *coupling[MAX_ANALOG_CHANNELS]; + + /* Current & configured device settings */ + float timebase; + char *trigger_source; + float horiz_triggerpos; + char *trigger_slope; + float trigger_level; + + /* Current & configured acquisition settings */ + gboolean average_enabled; + int average_samples; + gboolean peak_enabled; + enum DRIVER_CAPTURE_MODE capture_mode; + + /* Acquisition state */ + enum wait_events acquire_status; + struct most_recent_wave_preamble wavepre; + gboolean prior_state_running; + gboolean prior_state_single; + + uint64_t limit_frames; + uint64_t num_frames; + GSList *enabled_channels; + GSList *channel_entry; + + /* Acq buffers used for reading from the scope and sending data to app. + */ + unsigned char *buffer; + int num_block_read; +}; + +SR_PRIV int tektronix_tds_config_set( + const struct sr_dev_inst *sdi, const char *format, ...); +SR_PRIV int tektronix_tds_capture_start(const struct sr_dev_inst *sdi); +SR_PRIV int tektronix_tds_channel_start(const struct sr_dev_inst *sdi); +SR_PRIV int tektronix_tds_capture_finish(const struct sr_dev_inst *sdi); +SR_PRIV int tektronix_tds_receive(int fd, int revents, void *cb_data); +SR_PRIV int tektronix_tds_get_dev_cfg(const struct sr_dev_inst *sdi); +SR_PRIV int tektronix_tds_get_dev_cfg_vertical(const struct sr_dev_inst *sdi); +SR_PRIV int tektronix_tds_get_dev_cfg_horizontal(const struct sr_dev_inst *sdi); + +#endif diff --git a/src/scpi.h b/src/scpi.h index 634c27271..69df59fd8 100644 --- a/src/scpi.h +++ b/src/scpi.h @@ -104,79 +104,108 @@ struct sr_scpi_dev_inst { int (*read_begin)(void *priv); int (*read_data)(void *priv, char *buf, int maxlen); int (*write_data)(void *priv, char *buf, int len); + int (*block_ends)(void *priv, char *buf, size_t len); int (*read_complete)(void *priv); int (*close)(struct sr_scpi_dev_inst *scpi); void (*free)(void *priv); - unsigned int read_timeout_us; + /* + * Read times out when 'read_timeout_us' passes while no data + * at all became available. Read is paused for 'read_pause_us' + * when a read attempt got zero receive data (avoid tight loops + * when devices are busy creating the response). + */ + int64_t read_timeout_us; + int64_t read_pause_us; void *priv; /* Only used for quirk workarounds, notably the Rigol DS1000 series. */ uint64_t firmware_version; GMutex scpi_mutex; - char *actual_channel_name; + char *curr_channel_name; gboolean no_opc_command; }; +/** + * Callback to find a data block after optional leading response text. + * + * @param[in] cb_data Caller provided context information. + * @param[in] rsp_data Previously accumulated response data. + * @param[in] rsp_dlen Length of accumulated response data. + * @param[out] block_off Position of block start in response. + * + * @returns Non-zero positive when more input data is required. + * SR_OK when the block start position was determined. + * SR_ERR* when fatal errors were seen. + */ +typedef int (*sr_scpi_block_find_cb)(void *cb_data, + const char *rsp_data, size_t rsp_dlen, size_t *block_off); + SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options, - struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)); + struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)); SR_PRIV struct sr_scpi_dev_inst *scpi_dev_inst_new(struct drv_context *drvc, - const char *resource, const char *serialcomm); + const char *resource, const char *serialcomm); SR_PRIV int sr_scpi_open(struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_connection_id(struct sr_scpi_dev_inst *scpi, - char **connection_id); + char **connection_id); SR_PRIV int sr_scpi_source_add(struct sr_session *session, - struct sr_scpi_dev_inst *scpi, int events, int timeout, - sr_receive_data_callback cb, void *cb_data); + struct sr_scpi_dev_inst *scpi, int events, int timeout, + sr_receive_data_callback cb, void *cb_data); SR_PRIV int sr_scpi_source_remove(struct sr_session *session, - struct sr_scpi_dev_inst *scpi); + struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_send(struct sr_scpi_dev_inst *scpi, - const char *format, ...); + const char *format, ...); SR_PRIV int sr_scpi_send_variadic(struct sr_scpi_dev_inst *scpi, - const char *format, va_list args); + const char *format, va_list args); SR_PRIV int sr_scpi_read_begin(struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_read_data(struct sr_scpi_dev_inst *scpi, char *buf, int maxlen); SR_PRIV int sr_scpi_write_data(struct sr_scpi_dev_inst *scpi, char *buf, int len); +SR_PRIV int sr_scpi_block_ends(struct sr_scpi_dev_inst *scpi, + char *buf, size_t len); SR_PRIV int sr_scpi_read_complete(struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_close(struct sr_scpi_dev_inst *scpi); SR_PRIV void sr_scpi_free(struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_read_response(struct sr_scpi_dev_inst *scpi, - GString *response, gint64 abs_timeout_us); + GString *response, gint64 abs_timeout_us); SR_PRIV int sr_scpi_get_string(struct sr_scpi_dev_inst *scpi, - const char *command, char **scpi_response); + const char *command, char **scpi_response); SR_PRIV int sr_scpi_get_bool(struct sr_scpi_dev_inst *scpi, - const char *command, gboolean *scpi_response); + const char *command, gboolean *scpi_response); SR_PRIV int sr_scpi_get_int(struct sr_scpi_dev_inst *scpi, - const char *command, int *scpi_response); + const char *command, int *scpi_response); SR_PRIV int sr_scpi_get_float(struct sr_scpi_dev_inst *scpi, - const char *command, float *scpi_response); + const char *command, float *scpi_response); SR_PRIV int sr_scpi_get_double(struct sr_scpi_dev_inst *scpi, - const char *command, double *scpi_response); + const char *command, double *scpi_response); SR_PRIV int sr_scpi_get_opc(struct sr_scpi_dev_inst *scpi); SR_PRIV int sr_scpi_get_floatv(struct sr_scpi_dev_inst *scpi, - const char *command, GArray **scpi_response); + const char *command, GArray **scpi_response); SR_PRIV int sr_scpi_get_uint8v(struct sr_scpi_dev_inst *scpi, - const char *command, GArray **scpi_response); + const char *command, GArray **scpi_response); SR_PRIV int sr_scpi_get_data(struct sr_scpi_dev_inst *scpi, - const char *command, GString **scpi_response); + const char *command, GString **scpi_response); SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, - const char *command, GByteArray **scpi_response); + const char *command, GByteArray **scpi_response); +SR_PRIV int sr_scpi_get_text_then_block(struct sr_scpi_dev_inst *scpi, + const char *command, + sr_scpi_block_find_cb cb_func, void *cb_data, + GString **scpi_text_response, GByteArray **scpi_block_response); SR_PRIV int sr_scpi_get_hw_id(struct sr_scpi_dev_inst *scpi, - struct sr_scpi_hw_info **scpi_response); + struct sr_scpi_hw_info **scpi_response); SR_PRIV void sr_scpi_hw_info_free(struct sr_scpi_hw_info *hw_info); SR_PRIV const char *sr_scpi_unquote_string(char *s); SR_PRIV const char *sr_vendor_alias(const char *raw_vendor); SR_PRIV const char *sr_scpi_cmd_get(const struct scpi_command *cmdtable, - int command); + int command); SR_PRIV int sr_scpi_cmd(const struct sr_dev_inst *sdi, - const struct scpi_command *cmdtable, - int channel_command, const char *channel_name, - int command, ...); + const struct scpi_command *cmdtable, + int channel_command, const char *channel_name, + int command, ...); SR_PRIV int sr_scpi_cmd_resp(const struct sr_dev_inst *sdi, - const struct scpi_command *cmdtable, - int channel_command, const char *channel_name, - GVariant **gvar, const GVariantType *gvtype, int command, ...); + const struct scpi_command *cmdtable, + int channel_command, const char *channel_name, + GVariant **gvar, const GVariantType *gvtype, int command, ...); /*--- GPIB only functions ---------------------------------------------------*/ diff --git a/src/scpi/scpi.c b/src/scpi/scpi.c index 8d24ceea9..a3a109460 100644 --- a/src/scpi/scpi.c +++ b/src/scpi/scpi.c @@ -27,8 +27,8 @@ #define LOG_PREFIX "scpi" -#define SCPI_READ_RETRIES 100 -#define SCPI_READ_RETRY_TIMEOUT_US (10 * 1000) +#define SCPI_OPC_RETRY_COUNT 100 +#define SCPI_OPC_RETRY_DELAY_US (10 * 1000) static const char *scpi_vendors[][2] = { { "Agilent Technologies", "Agilent" }, @@ -107,8 +107,8 @@ static const struct sr_scpi_dev_inst *scpi_devs[] = { }; static struct sr_dev_inst *sr_scpi_scan_resource(struct drv_context *drvc, - const char *resource, const char *serialcomm, - struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)) + const char *resource, const char *serialcomm, + struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)) { struct sr_scpi_dev_inst *scpi; struct sr_dev_inst *sdi; @@ -144,7 +144,7 @@ static struct sr_dev_inst *sr_scpi_scan_resource(struct drv_context *drvc, * @return SR_OK on success, SR_ERR on failure. */ static int scpi_send_variadic(struct sr_scpi_dev_inst *scpi, - const char *format, va_list args) + const char *format, va_list args) { va_list args_copy; char *buf; @@ -221,6 +221,29 @@ static int scpi_read_data(struct sr_scpi_dev_inst *scpi, char *buf, int maxlen) return scpi->read_data(scpi->priv, buf, maxlen); } +/** + * Make sure a string buffer contains free space, chunked resize. + * + * Allocates more space ('add_space' in addition to existing content) + * when free buffer space is below 'threshold'. + * + * @param[in] buf The buffer where free space is desired. + * @param[in] threshold Minimum amount of free space accepted. + * @param[in] add_space Chunk to ensure when buffer gets extended. + */ +static void scpi_make_string_space(GString *buf, + size_t threshold, size_t add_space) +{ + size_t have_space, prev_size; + + have_space = buf->allocated_len - buf->len; + if (have_space < threshold) { + prev_size = buf->len; + g_string_set_size(buf, prev_size + add_space); + g_string_set_size(buf, prev_size); + } +} + /** * Do a non-blocking read of up to the allocated length, and * check if a timeout has occured, without mutex. @@ -232,7 +255,7 @@ static int scpi_read_data(struct sr_scpi_dev_inst *scpi, char *buf, int maxlen) * @return read length on success, SR_ERR* on failure. */ static int scpi_read_response(struct sr_scpi_dev_inst *scpi, - GString *response, gint64 abs_timeout_us) + GString *response, gint64 abs_timeout_us) { int len, space; @@ -261,18 +284,17 @@ static int scpi_read_response(struct sr_scpi_dev_inst *scpi, * Send a SCPI command, receive the reply and store the reply in * scpi_response, without mutex. * - * @param scpi Previously initialised SCPI device structure. - * @param command The SCPI command to send to the device. - * @param scpi_response Pointer where to store the SCPI response. + * @param[in] scpi Previously initialised SCPI device structure. + * @param[in] command The SCPI command to send to the device. + * @param[out] scpi_response Pointer where to store the SCPI response. * * @return SR_OK on success, SR_ERR on failure. */ static int scpi_get_data(struct sr_scpi_dev_inst *scpi, - const char *command, GString **scpi_response) + const char *command, GString **scpi_response) { int ret; GString *response; - int space; gint64 timeout; /* Optionally send caller provided command. */ @@ -286,33 +308,27 @@ static int scpi_get_data(struct sr_scpi_dev_inst *scpi, return SR_ERR; /* Keep reading until completion or until timeout. */ - timeout = g_get_monotonic_time() + scpi->read_timeout_us; - response = *scpi_response; - + timeout = g_get_monotonic_time() + scpi->read_timeout_us; while (!sr_scpi_read_complete(scpi)) { /* Resize the buffer when free space drops below a threshold. */ - space = response->allocated_len - response->len; - if (space < 128) { - int oldlen = response->len; - g_string_set_size(response, oldlen + 1024); - g_string_set_size(response, oldlen); - } + scpi_make_string_space(response, 128, 1024); /* Read another chunk of the response. */ ret = scpi_read_response(scpi, response, timeout); - if (ret < 0) return ret; if (ret > 0) timeout = g_get_monotonic_time() + scpi->read_timeout_us; + if (ret == 0 && scpi->read_pause_us) + g_usleep(scpi->read_pause_us); } return SR_OK; } SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options, - struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)) + struct sr_dev_inst *(*probe_device)(struct sr_scpi_dev_inst *scpi)) { GSList *resources, *l, *devices; struct sr_dev_inst *sdi; @@ -364,33 +380,36 @@ SR_PRIV GSList *sr_scpi_scan(struct drv_context *drvc, GSList *options, } SR_PRIV struct sr_scpi_dev_inst *scpi_dev_inst_new(struct drv_context *drvc, - const char *resource, const char *serialcomm) + const char *resource, const char *serialcomm) { - struct sr_scpi_dev_inst *scpi = NULL; + size_t i; const struct sr_scpi_dev_inst *scpi_dev; + struct sr_scpi_dev_inst *scpi; gchar **params; - unsigned i; + int ret; for (i = 0; i < ARRAY_SIZE(scpi_devs); i++) { scpi_dev = scpi_devs[i]; - if (!strncmp(resource, scpi_dev->prefix, strlen(scpi_dev->prefix))) { - sr_dbg("Opening %s device %s.", scpi_dev->name, resource); - scpi = g_malloc(sizeof(*scpi)); - *scpi = *scpi_dev; - scpi->priv = g_malloc0(scpi->priv_size); - scpi->read_timeout_us = 1000 * 1000; - params = g_strsplit(resource, "/", 0); - if (scpi->dev_inst_new(scpi->priv, drvc, resource, - params, serialcomm) != SR_OK) { - sr_scpi_free(scpi); - scpi = NULL; - } - g_strfreev(params); - break; + if (strncmp(resource, scpi_dev->prefix, strlen(scpi_dev->prefix)) != 0) + continue; + sr_dbg("Opening %s device %s.", scpi_dev->name, resource); + scpi = g_malloc0(sizeof(*scpi)); + *scpi = *scpi_dev; + scpi->priv = g_malloc0(scpi->priv_size); + scpi->read_timeout_us = 1000 * 1000; + scpi->read_pause_us = 0; + params = g_strsplit(resource, "/", 0); + ret = scpi->dev_inst_new(scpi->priv, drvc, + resource, params, serialcomm); + g_strfreev(params); + if (ret != SR_OK) { + sr_scpi_free(scpi); + scpi = NULL; } + return scpi; } - return scpi; + return NULL; } /** @@ -419,7 +438,7 @@ SR_PRIV int sr_scpi_open(struct sr_scpi_dev_inst *scpi) * @return SR_OK on success, SR_ERR on failure. */ SR_PRIV int sr_scpi_connection_id(struct sr_scpi_dev_inst *scpi, - char **connection_id) + char **connection_id) { return scpi->connection_id(scpi, connection_id); } @@ -438,8 +457,8 @@ SR_PRIV int sr_scpi_connection_id(struct sr_scpi_dev_inst *scpi, * SR_ERR_MALLOC upon memory allocation errors. */ SR_PRIV int sr_scpi_source_add(struct sr_session *session, - struct sr_scpi_dev_inst *scpi, int events, int timeout, - sr_receive_data_callback cb, void *cb_data) + struct sr_scpi_dev_inst *scpi, int events, int timeout, + sr_receive_data_callback cb, void *cb_data) { return scpi->source_add(session, scpi->priv, events, timeout, cb, cb_data); } @@ -455,7 +474,7 @@ SR_PRIV int sr_scpi_source_add(struct sr_session *session, * internal errors. */ SR_PRIV int sr_scpi_source_remove(struct sr_session *session, - struct sr_scpi_dev_inst *scpi) + struct sr_scpi_dev_inst *scpi) { return scpi->source_remove(session, scpi->priv); } @@ -469,7 +488,7 @@ SR_PRIV int sr_scpi_source_remove(struct sr_session *session, * @return SR_OK on success, SR_ERR on failure. */ SR_PRIV int sr_scpi_send(struct sr_scpi_dev_inst *scpi, - const char *format, ...) + const char *format, ...) { va_list args; int ret; @@ -493,7 +512,7 @@ SR_PRIV int sr_scpi_send(struct sr_scpi_dev_inst *scpi, * @return SR_OK on success, SR_ERR on failure. */ SR_PRIV int sr_scpi_send_variadic(struct sr_scpi_dev_inst *scpi, - const char *format, va_list args) + const char *format, va_list args) { int ret; @@ -526,7 +545,7 @@ SR_PRIV int sr_scpi_read_begin(struct sr_scpi_dev_inst *scpi) * @return Number of bytes read, or SR_ERR upon failure. */ SR_PRIV int sr_scpi_read_data(struct sr_scpi_dev_inst *scpi, - char *buf, int maxlen) + char *buf, int maxlen) { int ret; @@ -550,7 +569,7 @@ SR_PRIV int sr_scpi_read_data(struct sr_scpi_dev_inst *scpi, * @return Number of bytes read, or SR_ERR upon failure. */ SR_PRIV int sr_scpi_write_data(struct sr_scpi_dev_inst *scpi, - char *buf, int maxlen) + char *buf, int maxlen) { int ret; @@ -561,6 +580,29 @@ SR_PRIV int sr_scpi_write_data(struct sr_scpi_dev_inst *scpi, return ret; } +/** + * Re-check whether remaining payload data contains "end of message". + * + * This executes after a binary block has completed, and is passed + * the remaining previously accumulated receive data. It is only + * important to those physical transports where "end of message" is + * communicated by means of payload data, not in handshake signals + * that are outside of payload bytes. + * + * @param scpi Previously initialized SCPI device structure. + * @param buf Buffer position after binary block. + * @param len Number of bytes that were received after the block. + * + * @return zero upon success, non-zero upon failure. + */ +SR_PRIV int sr_scpi_block_ends(struct sr_scpi_dev_inst *scpi, + char *buf, size_t len) +{ + if (scpi->block_ends) + return scpi->block_ends(scpi->priv, buf, len); + return SR_OK; +} + /** * Check whether a complete SCPI response has been received. * @@ -605,7 +647,7 @@ SR_PRIV void sr_scpi_free(struct sr_scpi_dev_inst *scpi) scpi->free(scpi->priv); g_free(scpi->priv); - g_free(scpi->actual_channel_name); + g_free(scpi->curr_channel_name); g_free(scpi); } @@ -622,7 +664,7 @@ SR_PRIV void sr_scpi_free(struct sr_scpi_dev_inst *scpi) * @return SR_OK on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_string(struct sr_scpi_dev_inst *scpi, - const char *command, char **scpi_response) + const char *command, char **scpi_response) { GString *response; @@ -662,7 +704,7 @@ SR_PRIV int sr_scpi_get_string(struct sr_scpi_dev_inst *scpi, * @return read length on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_read_response(struct sr_scpi_dev_inst *scpi, - GString *response, gint64 abs_timeout_us) + GString *response, gint64 abs_timeout_us) { int ret; @@ -674,7 +716,7 @@ SR_PRIV int sr_scpi_read_response(struct sr_scpi_dev_inst *scpi, } SR_PRIV int sr_scpi_get_data(struct sr_scpi_dev_inst *scpi, - const char *command, GString **scpi_response) + const char *command, GString **scpi_response) { int ret; @@ -696,7 +738,7 @@ SR_PRIV int sr_scpi_get_data(struct sr_scpi_dev_inst *scpi, * @return SR_OK on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_bool(struct sr_scpi_dev_inst *scpi, - const char *command, gboolean *scpi_response) + const char *command, gboolean *scpi_response) { int ret; char *response; @@ -728,7 +770,7 @@ SR_PRIV int sr_scpi_get_bool(struct sr_scpi_dev_inst *scpi, * @return SR_OK on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_int(struct sr_scpi_dev_inst *scpi, - const char *command, int *scpi_response) + const char *command, int *scpi_response) { int ret; struct sr_rational ret_rational; @@ -765,7 +807,7 @@ SR_PRIV int sr_scpi_get_int(struct sr_scpi_dev_inst *scpi, * @return SR_OK on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_float(struct sr_scpi_dev_inst *scpi, - const char *command, float *scpi_response) + const char *command, float *scpi_response) { int ret; char *response; @@ -797,7 +839,7 @@ SR_PRIV int sr_scpi_get_float(struct sr_scpi_dev_inst *scpi, * @return SR_OK on success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_double(struct sr_scpi_dev_inst *scpi, - const char *command, double *scpi_response) + const char *command, double *scpi_response) { int ret; char *response; @@ -828,15 +870,18 @@ SR_PRIV int sr_scpi_get_double(struct sr_scpi_dev_inst *scpi, */ SR_PRIV int sr_scpi_get_opc(struct sr_scpi_dev_inst *scpi) { - unsigned int i; + unsigned int retries; gboolean opc; + int ret; - for (i = 0; i < SCPI_READ_RETRIES; i++) { + retries = SCPI_OPC_RETRY_COUNT; + while (retries--) { opc = FALSE; - sr_scpi_get_bool(scpi, SCPI_CMD_OPC, &opc); - if (opc) + ret = sr_scpi_get_bool(scpi, SCPI_CMD_OPC, &opc); + if (ret == SR_OK && opc) return SR_OK; - g_usleep(SCPI_READ_RETRY_TIMEOUT_US); + if (retries) + g_usleep(SCPI_OPC_RETRY_DELAY_US); } return SR_ERR; @@ -857,7 +902,7 @@ SR_PRIV int sr_scpi_get_opc(struct sr_scpi_dev_inst *scpi) * error or upon no response. */ SR_PRIV int sr_scpi_get_floatv(struct sr_scpi_dev_inst *scpi, - const char *command, GArray **scpi_response) + const char *command, GArray **scpi_response) { int ret; float tmp; @@ -917,7 +962,7 @@ SR_PRIV int sr_scpi_get_floatv(struct sr_scpi_dev_inst *scpi, * error or upon no response. */ SR_PRIV int sr_scpi_get_uint8v(struct sr_scpi_dev_inst *scpi, - const char *command, GArray **scpi_response) + const char *command, GArray **scpi_response) { int tmp, ret; char *response; @@ -961,74 +1006,155 @@ SR_PRIV int sr_scpi_get_uint8v(struct sr_scpi_dev_inst *scpi, return ret; } -/** - * Send a SCPI command, read the reply, parse it as binary data with a - * "definite length block" header and store the as an result in scpi_response. - * - * Callers must free the allocated memory (unless it's NULL) regardless of - * the routine's return code. See @ref g_byte_array_free(). - * - * @param[in] scpi Previously initialised SCPI device structure. - * @param[in] command The SCPI command to send to the device (can be NULL). - * @param[out] scpi_response Pointer where to store the parsed result. - * - * @return SR_OK upon successfully parsing all values, SR_ERR* upon a parsing - * error or upon no response. - */ -SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, - const char *command, GByteArray **scpi_response) +/* Internal, serves both sr_scpi_get_block(), sr_scpi_get_text_then_block(). */ +static int scpi_get_block_int(struct sr_scpi_dev_inst *scpi, + const char *command, + sr_scpi_block_find_cb cb_func, void *cb_data, + GString **text_response, GByteArray **block_response) { int ret; - GString* response; - gsize oldlen; + GString *response; + size_t prev_size; + size_t blk_off; char buf[10]; - long llen; - long datalen; + unsigned long ul_value; + size_t digits_count, bytes_count; gint64 timeout; + size_t trail_count; + uint8_t *blk_bytes; - *scpi_response = NULL; + if (text_response) + *text_response = NULL; + if (block_response) + *block_response = NULL; g_mutex_lock(&scpi->scpi_mutex); - if (command) - if (scpi_send(scpi, command) != SR_OK) { + /* Optionally send a caller provided command. */ + if (command) { + ret = scpi_send(scpi, command); + sr_dbg("SCPI get block, sent command, ret %d.", ret); + if (ret != SR_OK) { g_mutex_unlock(&scpi->scpi_mutex); return SR_ERR; } + } - if (sr_scpi_read_begin(scpi) != SR_OK) { + /* Start reading a reponse message. */ + ret = sr_scpi_read_begin(scpi); + sr_dbg("SCPI get block, read begin, ret %d.", ret); + if (ret != SR_OK) { g_mutex_unlock(&scpi->scpi_mutex); return SR_ERR; } /* - * Assume an initial maximum length, optionally gets adjusted below. - * Prepare a NULL return value for when error paths will be taken. + * Get an optional leading text before the data block when the + * caller provided routine can determine the boundary between + * the text and the block parts of the response. Absence of a + * boundary finder assumes that the response is just a block. */ response = g_string_sized_new(1024); - timeout = g_get_monotonic_time() + scpi->read_timeout_us; + while (cb_func) { + /* Extend buffer space in sensible chunks. */ + scpi_make_string_space(response, 128, 1024); + /* Accumulate more receive data. */ + ret = scpi_read_response(scpi, response, timeout); + sr_dbg("SCPI get block, text read, ret %d, len %zu.", + ret, response->len); + if (ret < 0) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + if (ret == 0) { + if (scpi->read_pause_us) + g_usleep(scpi->read_pause_us); + continue; + } + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + /* + * Run caller's routine to find the start of the block. + * Negative return is a fatal error. Zero return found + * the offset. Non-zero positive return needs more data. + */ + blk_off = 0; + ret = cb_func(cb_data, response->str, response->len, &blk_off); + sr_dbg("SCPI get block, text check, ret %d, off %zu.", + ret, blk_off); + if (ret < 0) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + if (ret > 0) + continue; + /* Block boundary detected. Grab text before the block. */ + if (blk_off > response->len) + blk_off = response->len; + if (text_response) + *text_response = g_string_new_len(response->str, blk_off); + g_string_erase(response, 0, blk_off); + break; + } - /* Get (the first chunk of) the response. */ - do { + /* + * Get (the first chunk of) the block part of the response. + * + * When we get here, there either is no text before the block, + * or the text before the block was already taken above. Either + * way, remaining code exclusively deals with the data block. + * Get enough receive data to determine the block's length. + */ + while (response->len < 2) { ret = scpi_read_response(scpi, response, timeout); + sr_dbg("SCPI get block, block start read, ret %d, len %zu.", + ret, response->len); if (ret < 0) { g_mutex_unlock(&scpi->scpi_mutex); g_string_free(response, TRUE); return ret; } - } while (response->len < 2); + if (ret == 0 && scpi->read_pause_us) + g_usleep(scpi->read_pause_us); + } /* + * Get the data block length, then strip off that length detail + * from the input buffer, leaving just the data bytes which were + * received so far. Then read more bytes when necessary. + * * SCPI protocol data blocks are preceeded with a length spec. * The length spec consists of a '#' marker, one digit which * specifies the character count of the length spec, and the * respective number of characters which specify the data block's - * length. Raw data bytes follow (thus one must no longer assume - * that the received input stream would be an ASCIIZ string). + * length. Raw data bytes follow, thus one must no longer assume + * that the received input stream would be an ASCII string, or + * that CR or LF characters had a meaning within that block which + * corresponds to message termination. Example: + * #210abcdefghij * - * Get the data block length, and strip off the length spec from - * the input buffer, leaving just the data bytes. + * Zero length is legal but unsupported in this implementation. + * It is referred to as "indefinite length block", the number of + * bytes is unknown or is not communicated in advance. The end + * of the bytes sequence depends on the physical transport's + * end-of-message condition. Which is only reliably available + * in those transports which communicate message length or end + * out of band, separate from the response text or raw bytes. + * The use of indefinite length blocks in firmware is considered + * rare. Whether or not a trailing LF character must be seen as + * part of the raw bytes sequence is yet to get determined. Emit + * a warning for users' and developers' awareness when we see + * this response format. Examples: + * #0bytes_of_unknown_length_with_uncertain_end_condition + * #10not_supported_either + * + * The SCPI 1999.0 specification (see page 220 and following in + * the "HCOPy" description) references IEEE 488.2, especially + * section 8.7.9 for DEFINITE LENGTH and section 8.7.10 for + * INDEFINITE LENGTH ARBITRARY BLOCK RESPONSE DATA. The latter + * with a leading "#0" length and a trailing "NL^END" marker. */ if (response->str[0] != '#') { g_mutex_unlock(&scpi->scpi_mutex); @@ -1037,26 +1163,8 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, } buf[0] = response->str[1]; buf[1] = '\0'; - ret = sr_atol(buf, &llen); - /* - * The form "#0..." is legal, and does not mean "empty response", - * but means that the number of data bytes is not known (or was - * not communicated) at this time. Instead the block ends at an - * "END MESSAGE" termination sequence. Which translates to active - * EOI while a text line termination is sent (CR or LF, and this - * text line termination is not part of the block's data value). - * Since this kind of #0... response is considered rare, and - * depends on specific support in physical transports underneath - * the SCPI layer, let's flag the condition and bail out with an - * error here, until it's found to be a genuine issue in the field. - * - * The SCPI 1999.0 specification (see page 220 and following in - * the "HCOPy" description) references IEEE 488.2, especially - * section 8.7.9 for DEFINITE LENGTH and section 8.7.10 for - * INDEFINITE LENGTH ARBITRARY BLOCK RESPONSE DATA. The latter - * with a leading "#0" length and a trailing "NL^END" marker. - */ - if (ret == SR_OK && !llen) { + ret = sr_atoul_base(buf, &ul_value, NULL, 10); + if (ret == SR_OK && !ul_value) { sr_err("unsupported INDEFINITE LENGTH ARBITRARY BLOCK RESPONSE"); ret = SR_ERR_NA; } @@ -1065,65 +1173,174 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, g_string_free(response, TRUE); return ret; } + digits_count = ul_value; - while (response->len < (unsigned long)(2 + llen)) { + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + while (response->len < 2 + digits_count) { ret = scpi_read_response(scpi, response, timeout); + sr_dbg("SCPI get block, block len read, ret %d, len %zu.", + ret, response->len); if (ret < 0) { g_mutex_unlock(&scpi->scpi_mutex); g_string_free(response, TRUE); return ret; } + if (ret == 0 && scpi->read_pause_us) + g_usleep(scpi->read_pause_us); } - - memcpy(buf, &response->str[2], llen); - buf[llen] = '\0'; - ret = sr_atol(buf, &datalen); - if ((ret != SR_OK) || (datalen == 0)) { + memcpy(buf, &response->str[2], digits_count); + buf[digits_count] = '\0'; + ret = sr_atoul_base(buf, &ul_value, NULL, 10); + if (ret == SR_OK && !ul_value) { + sr_err("unsupported INDEFINITE LENGTH ARBITRARY BLOCK RESPONSE"); + ret = SR_ERR_NA; + } + if (ret != SR_OK) { g_mutex_unlock(&scpi->scpi_mutex); g_string_free(response, TRUE); return ret; } - g_string_erase(response, 0, 2 + llen); + bytes_count = ul_value; + + sr_spew("SCPI get block, text %*s, digits %zu, number %zu.", + (int)(2 + digits_count), response->str, + digits_count, bytes_count); + sr_dbg("SCPI get block, bytes count %zu.", bytes_count); + g_string_erase(response, 0, 2 + digits_count); + + /* + * Re-allocate the buffer size to the now known bytes count + * (and include some more space for response termination). + * Keep reading more chunks of response data as necessary. + * + * Do not stall here for incomplete reads. Truncate the data + * and return the partial response upon timeouts (bug 1323). + */ + prev_size = response->len; + g_string_set_size(response, bytes_count + 16); + g_string_set_size(response, prev_size); + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + while (response->len < bytes_count) { + prev_size = response->len; + ret = scpi_read_response(scpi, response, timeout); + sr_dbg("SCPI get block, read block, ret %d, len %zu.", + ret, response->len); + if (ret == SR_ERR_TIMEOUT) { + sr_dbg("SCPI get block, timeout, had %zu, cap to %zu..", + response->len, prev_size); + bytes_count = prev_size; + break; + } + if (ret < 0) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + if (ret == 0) { + if (scpi->read_pause_us) + g_usleep(scpi->read_pause_us); + continue; + } + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + } /* - * Re-allocate the buffer size to the now known length - * and keep reading more chunks of response data. + * Depending on underlying physical transports the data block + * could be followed by more data which signals end-of-message. + * Keep reading until the transport detects the response's + * completion. Tell transports that binary mode has ended. */ - oldlen = response->len; - g_string_set_size(response, datalen); - g_string_set_size(response, oldlen); - - if (oldlen < (unsigned long)(datalen)) { - do { - oldlen = response->len; - ret = scpi_read_response(scpi, response, timeout); - - /* On timeout truncate the buffer and send the partial response - * instead of getting stuck on timeouts... - */ - if (ret == SR_ERR_TIMEOUT) { - datalen = oldlen; - break; - } - if (ret < 0) { - g_mutex_unlock(&scpi->scpi_mutex); - g_string_free(response, TRUE); - return ret; - } - if (ret > 0) - timeout = g_get_monotonic_time() + scpi->read_timeout_us; - } while (response->len < (unsigned long)(datalen)); + trail_count = response->len - bytes_count; + sr_dbg("SCPI get block, block ends, trail length %zu.", trail_count); + sr_scpi_block_ends(scpi, &response->str[bytes_count], trail_count); + timeout = g_get_monotonic_time() + scpi->read_timeout_us; + while (!sr_scpi_read_complete(scpi)) { + scpi_make_string_space(response, 4, 16); + ret = scpi_read_response(scpi, response, timeout); + sr_dbg("SCPI get block, read end-of-message, ret %d.", ret); + if (ret < 0) { + g_mutex_unlock(&scpi->scpi_mutex); + g_string_free(response, TRUE); + return ret; + } + if (ret == 0 && scpi->read_pause_us) + g_usleep(scpi->read_pause_us); } g_mutex_unlock(&scpi->scpi_mutex); /* Convert received data to byte array. */ - *scpi_response = g_byte_array_new_take( - (guint8*)g_string_free(response, FALSE), datalen); + sr_dbg("SCPI get block, got block, return bytes %zu.", bytes_count); + if (block_response) { + blk_bytes = (uint8_t *)g_string_free(response, FALSE); + *block_response = g_byte_array_new_take(blk_bytes, bytes_count); + } else { + g_string_free(response, TRUE); + } return SR_OK; } +/** + * Send a SCPI command, read the reply, parse it as binary data with a + * "definite length block" header, while optional text can precede the + * binary block. + * + * Callers must provide a routine which determines the position in the + * response message where the block starts. In the absence of a block + * finding routine the response is assumed to be a block only without + * leading text. Common SCPI support code will keep reading until the + * caller's routine either finds the start of the block, or communicates + * an error condition. Callers must not modify the input text during + * block search, the routine can get invoked arbitrary number of times. + * + * Callers must free the allocated memory (unless it's #NULL) regardless of + * the routine's exit code. See @ref g_string_free(), @ref g_byte_array_free(). + * + * @param[in] scpi Previously initialised SCPI device structure. + * @param[in] command The SCPI command to send to the device (can be NULL). + * @param[in] cb_func Caller provided routine to find the start of the block. + * @param[in] cb_data Context for caller provided callback routine. + * @param[out] text_response Pointer where to store the text result. + * @param[out] block_response Pointer where to store the bytes result. + * + * @return SR_OK upon successfully parsing all values, SR_ERR* upon a parsing + * error or upon no response. + */ +SR_PRIV int sr_scpi_get_text_then_block(struct sr_scpi_dev_inst *scpi, + const char *command, + sr_scpi_block_find_cb cb_func, void *cb_data, + GString **text_response, GByteArray **block_response) +{ + return scpi_get_block_int(scpi, command, cb_func, cb_data, + text_response, block_response); +} + +/** + * Send a SCPI command, read the reply, parse it as binary data with a + * "definite length block" header and store the result in scpi_response. + * + * Callers must free the allocated memory (unless it's NULL) regardless of + * the routine's return code. See @ref g_byte_array_free(). + * + * The "indefinite length block" is not supported by this implementation. + * Because not all supported physical transports signal the end-of-message + * condition out of band. + * + * @param[in] scpi Previously initialised SCPI device structure. + * @param[in] command The SCPI command to send to the device (can be NULL). + * @param[out] scpi_response Pointer where to store the parsed result. + * + * @return SR_OK upon successfully parsing all values, SR_ERR* upon a parsing + * error or upon no response. + */ +SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, + const char *command, GByteArray **scpi_response) +{ + return scpi_get_block_int(scpi, command, NULL, NULL, + NULL, scpi_response); +} + /** * Send the *IDN? SCPI command, receive the reply, parse it and store the * reply as a sr_scpi_hw_info structure in the supplied scpi_response pointer. @@ -1137,7 +1354,7 @@ SR_PRIV int sr_scpi_get_block(struct sr_scpi_dev_inst *scpi, * @return SR_OK upon success, SR_ERR* on failure. */ SR_PRIV int sr_scpi_get_hw_id(struct sr_scpi_dev_inst *scpi, - struct sr_scpi_hw_info **scpi_response) + struct sr_scpi_hw_info **scpi_response) { int num_tokens, ret; char *response; @@ -1274,7 +1491,7 @@ SR_PRIV const char *sr_vendor_alias(const char *raw_vendor) } SR_PRIV const char *sr_scpi_cmd_get(const struct scpi_command *cmdtable, - int command) + int command) { unsigned int i; const char *cmd; @@ -1294,9 +1511,9 @@ SR_PRIV const char *sr_scpi_cmd_get(const struct scpi_command *cmdtable, } SR_PRIV int sr_scpi_cmd(const struct sr_dev_inst *sdi, - const struct scpi_command *cmdtable, - int channel_command, const char *channel_name, - int command, ...) + const struct scpi_command *cmdtable, + int channel_command, const char *channel_name, + int command, ...) { struct sr_scpi_dev_inst *scpi; va_list args; @@ -1316,10 +1533,10 @@ SR_PRIV int sr_scpi_cmd(const struct sr_dev_inst *sdi, /* Select channel. */ channel_cmd = sr_scpi_cmd_get(cmdtable, channel_command); if (channel_cmd && channel_name && - g_strcmp0(channel_name, scpi->actual_channel_name)) { + g_strcmp0(channel_name, scpi->curr_channel_name)) { sr_spew("sr_scpi_cmd(): new channel = %s", channel_name); - g_free(scpi->actual_channel_name); - scpi->actual_channel_name = g_strdup(channel_name); + g_free(scpi->curr_channel_name); + scpi->curr_channel_name = g_strdup(channel_name); ret = scpi_send(scpi, channel_cmd, channel_name); if (ret != SR_OK) return ret; @@ -1335,9 +1552,9 @@ SR_PRIV int sr_scpi_cmd(const struct sr_dev_inst *sdi, } SR_PRIV int sr_scpi_cmd_resp(const struct sr_dev_inst *sdi, - const struct scpi_command *cmdtable, - int channel_command, const char *channel_name, - GVariant **gvar, const GVariantType *gvtype, int command, ...) + const struct scpi_command *cmdtable, + int channel_command, const char *channel_name, + GVariant **gvar, const GVariantType *gvtype, int command, ...) { struct sr_scpi_dev_inst *scpi; va_list args; @@ -1361,10 +1578,10 @@ SR_PRIV int sr_scpi_cmd_resp(const struct sr_dev_inst *sdi, /* Select channel. */ channel_cmd = sr_scpi_cmd_get(cmdtable, channel_command); if (channel_cmd && channel_name && - g_strcmp0(channel_name, scpi->actual_channel_name)) { + g_strcmp0(channel_name, scpi->curr_channel_name)) { sr_spew("sr_scpi_cmd_get(): new channel = %s", channel_name); - g_free(scpi->actual_channel_name); - scpi->actual_channel_name = g_strdup(channel_name); + g_free(scpi->curr_channel_name); + scpi->curr_channel_name = g_strdup(channel_name); ret = scpi_send(scpi, channel_cmd, channel_name); if (ret != SR_OK) return ret; diff --git a/src/scpi/scpi_serial.c b/src/scpi/scpi_serial.c index 067838adc..6bfcea84f 100644 --- a/src/scpi/scpi_serial.c +++ b/src/scpi/scpi_serial.c @@ -203,6 +203,19 @@ static int scpi_serial_read_data(void *priv, char *buf, int maxlen) return ret; } +static int scpi_serial_block_ends(void *priv, char *buf, size_t remlen) +{ + struct scpi_serial *sscpi; + + sscpi = priv; + + sscpi->got_newline = FALSE; + while (remlen--) + sscpi->got_newline |= *buf++ == '\n'; + + return SR_OK; +} + static int scpi_serial_read_complete(void *priv) { struct scpi_serial *sscpi = priv; @@ -238,6 +251,7 @@ SR_PRIV const struct sr_scpi_dev_inst scpi_serial_dev = { .send = scpi_serial_send, .read_begin = scpi_serial_read_begin, .read_data = scpi_serial_read_data, + .block_ends = scpi_serial_block_ends, .read_complete = scpi_serial_read_complete, .close = scpi_serial_close, .free = scpi_serial_free, diff --git a/src/scpi/scpi_tcp.c b/src/scpi/scpi_tcp.c index 29e7a4394..baef50e0d 100644 --- a/src/scpi/scpi_tcp.c +++ b/src/scpi/scpi_tcp.c @@ -120,7 +120,7 @@ static int scpi_tcp_connection_id(struct sr_scpi_dev_inst *scpi, { struct scpi_tcp *tcp = scpi->priv; - *connection_id = g_strdup_printf("%s/%s:%s", + *connection_id = g_strdup_printf("%s/%s/%s", scpi->prefix, tcp->address, tcp->port); return SR_OK;