]> sigrok.org Git - libsigrok.git/commitdiff
scpi-dmm: Implement support for Agilent 34405A, prepare others
authorGerhard Sittig <redacted>
Sun, 4 Nov 2018 18:54:17 +0000 (19:54 +0100)
committerUwe Hermann <redacted>
Sat, 10 Nov 2018 22:14:49 +0000 (23:14 +0100)
Implement the scpi-dmm driver in such a generic way that it could work
with several protocol variants and with differing models which happen to
use any of these protocol variants. Prepare a list of supported models
with their respective SCPI command set, set of DMM functions and their
precision.

Add support for Agilent 34405A. The ten functions of this device got
tested and are operational, in continuous mode as well as with sample
count or capture time limits. The driver can query the current meter's
function, can change the function, and can run acquisitions in either
the current mode or with a user specified function selection. There is
some potential for improvement: AUTO/MIN/MAX/HOLD indicators are not
supported by this implementation.

src/hardware/scpi-dmm/api.c
src/hardware/scpi-dmm/protocol.c
src/hardware/scpi-dmm/protocol.h

index f6018e469a98b43e3e8df1bd68cfcd6d66143f69..df758805a06ff3983fd7014ac985e35b10baed72 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
+#include <string.h>
 #include "protocol.h"
 
-static GSList *scan(struct sr_dev_driver *di, GSList *options)
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_MULTIMETER,
+};
+
+static const uint32_t devopts[] = {
+       SR_CONF_CONTINUOUS,
+       SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_MEASURED_QUANTITY | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+};
+
+static const struct scpi_command cmdset_agilent[] = {
+       { DMM_CMD_SETUP_REMOTE, "\n", },
+       { DMM_CMD_SETUP_FUNC, "CONF:%s", },
+       { DMM_CMD_QUERY_FUNC, "CONF?", },
+       { DMM_CMD_START_ACQ, "MEAS", },
+       { DMM_CMD_STOP_ACQ, "ABORT", },
+       { DMM_CMD_QUERY_VALUE, "READ?", },
+       { DMM_CMD_QUERY_PREC, "CONF?", },
+       ALL_ZERO,
+};
+
+static const struct mqopt_item mqopts_agilent_5digit[] = {
+       { SR_MQ_VOLTAGE, SR_MQFLAG_DC, "VOLT:DC", "VOLT ", NO_DFLT_PREC, },
+       { SR_MQ_VOLTAGE, SR_MQFLAG_AC, "VOLT:AC", "VOLT:AC ", NO_DFLT_PREC, },
+       { SR_MQ_CURRENT, SR_MQFLAG_DC, "CURR:DC", "CURR ", NO_DFLT_PREC, },
+       { SR_MQ_CURRENT, SR_MQFLAG_AC, "CURR:AC", "CURR:AC ", NO_DFLT_PREC, },
+       { SR_MQ_RESISTANCE, 0, "RES", "RES ", NO_DFLT_PREC, },
+       { SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, "FRES", "FRES ", NO_DFLT_PREC, },
+       { SR_MQ_CONTINUITY, 0, "CONT", "CONT", -1, },
+       { SR_MQ_CAPACITANCE, 0, "CAP", "CAP ", NO_DFLT_PREC, },
+       { SR_MQ_VOLTAGE, SR_MQFLAG_DC | SR_MQFLAG_DIODE, "DIOD", "DIOD", -4, },
+       { SR_MQ_TEMPERATURE, 0, "TEMP", "TEMP ", NO_DFLT_PREC, },
+       { SR_MQ_FREQUENCY, 0, "FREQ", "FREQ ", NO_DFLT_PREC, },
+};
+
+SR_PRIV const struct scpi_dmm_model models[] = {
+       {
+               "Agilent", "34405A",
+               1, 5, cmdset_agilent, ARRAY_AND_SIZE(mqopts_agilent_5digit),
+               scpi_dmm_get_meas_agilent,
+       },
+};
+
+static const struct scpi_dmm_model *is_compatible(const char *vendor, const char *model)
 {
-       struct drv_context *drvc;
-       GSList *devices;
+       size_t i;
+       const struct scpi_dmm_model *entry;
+
+       for (i = 0; i < ARRAY_SIZE(models); i++) {
+               entry = &models[i];
+               if (!entry->vendor || !entry->model)
+                       continue;
+               if (strcmp(vendor, entry->vendor) != 0)
+                       continue;
+               if (strcmp(model, entry->model) != 0)
+                       continue;
+               return entry;
+       }
 
-       (void)options;
+       return NULL;
+}
 
-       devices = NULL;
-       drvc = di->context;
-       drvc->instances = NULL;
+static struct sr_dev_inst *probe_device(struct sr_scpi_dev_inst *scpi)
+{
+       struct sr_scpi_hw_info *hw_info;
+       int ret;
+       const char *vendor;
+       const struct scpi_dmm_model *model;
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       size_t i;
+       gchar *channel_name;
+
+       ret = sr_scpi_get_hw_id(scpi, &hw_info);
+       scpi_dmm_cmd_delay(scpi);
+       if (ret != SR_OK) {
+               sr_info("Could not get IDN response.");
+               return NULL;
+       }
+       vendor = sr_vendor_alias(hw_info->manufacturer);
+       model = is_compatible(vendor, hw_info->model);
+       if (!model) {
+               sr_scpi_hw_info_free(hw_info);
+               return NULL;
+       }
 
-       /* TODO: scan for devices, either based on a SR_CONF_CONN option
-        * or on a USB scan. */
+       sdi = g_malloc0(sizeof(*sdi));
+       sdi->vendor = g_strdup(hw_info->manufacturer);
+       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 = &scpi_dmm_driver_info;
+       sdi->inst_type = SR_INST_SCPI;
+       sr_scpi_hw_info_free(hw_info);
+
+       devc = g_malloc0(sizeof(*devc));
+       sdi->priv = devc;
+       devc->num_channels = model->num_channels;
+       devc->cmdset = model->cmdset;
+       devc->model = model;
+
+       for (i = 0; i < devc->num_channels; i++) {
+               channel_name = g_strdup_printf("P%zu", i + 1);
+               sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, channel_name);
+       }
+
+       return sdi;
+}
 
-       return devices;
+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)
 {
-       (void)sdi;
+       struct sr_scpi_dev_inst *scpi;
+       int ret;
 
-       /* TODO: get handle from sdi->conn and open it. */
+       scpi = sdi->conn;
+       ret = sr_scpi_open(scpi);
+       if (ret < 0) {
+               sr_err("Failed to open SCPI device: %s.", sr_strerror(ret));
+               return SR_ERR;
+       }
 
        return SR_OK;
 }
 
 static int dev_close(struct sr_dev_inst *sdi)
 {
-       (void)sdi;
+       struct sr_scpi_dev_inst *scpi;
 
-       /* TODO: get handle from sdi->conn and close it. */
+       scpi = sdi->conn;
+       if (!scpi)
+               return SR_ERR_BUG;
 
-       return SR_OK;
+       sr_dbg("DIAG: sdi->status %d.", sdi->status - SR_ST_NOT_FOUND);
+       if (sdi->status <= SR_ST_INACTIVE)
+               return SR_OK;
+
+       return sr_scpi_close(scpi);
 }
 
 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;
+       enum sr_mq mq;
+       enum sr_mqflag mqflag;
+       GVariant *arr[2];
        int ret;
 
-       (void)sdi;
-       (void)data;
        (void)cg;
 
-       ret = SR_OK;
+       devc = sdi->priv;
+
        switch (key) {
-       /* TODO */
+       case SR_CONF_LIMIT_SAMPLES:
+       case SR_CONF_LIMIT_MSEC:
+               return sr_sw_limits_config_get(&devc->limits, key, data);
+       case SR_CONF_MEASURED_QUANTITY:
+               ret = scpi_dmm_get_mq(sdi, &mq, &mqflag, NULL);
+               if (ret != SR_OK)
+                       return ret;
+               arr[0] = g_variant_new_uint32(mq);
+               arr[1] = g_variant_new_uint64(mqflag);
+               *data = g_variant_new_tuple(arr, ARRAY_SIZE(arr));
+               return SR_OK;
        default:
                return SR_ERR_NA;
        }
-
-       return ret;
 }
 
 static int config_set(uint32_t key, GVariant *data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
-       int ret;
+       struct dev_context *devc;
+       enum sr_mq mq;
+       enum sr_mqflag mqflag;
+       GVariant *tuple_child;
 
-       (void)sdi;
-       (void)data;
        (void)cg;
 
-       ret = SR_OK;
+       devc = sdi->priv;
+
        switch (key) {
-       /* TODO */
+       case SR_CONF_LIMIT_SAMPLES:
+       case SR_CONF_LIMIT_MSEC:
+               return sr_sw_limits_config_set(&devc->limits, key, data);
+       case SR_CONF_MEASURED_QUANTITY:
+               tuple_child = g_variant_get_child_value(data, 0);
+               mq = g_variant_get_uint32(tuple_child);
+               tuple_child = g_variant_get_child_value(data, 1);
+               mqflag = g_variant_get_uint64(tuple_child);
+               g_variant_unref(tuple_child);
+               return scpi_dmm_set_mq(sdi, mq, mqflag);
        default:
-               ret = SR_ERR_NA;
+               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)
 {
-       int ret;
+       struct dev_context *devc;
+       GVariant *gvar, *arr[2];
+       GVariantBuilder gvb;
+       size_t i;
 
-       (void)sdi;
-       (void)data;
        (void)cg;
 
-       ret = SR_OK;
+       devc = sdi ? sdi->priv : NULL;
+
        switch (key) {
-       /* TODO */
+       case SR_CONF_SCAN_OPTIONS:
+       case SR_CONF_DEVICE_OPTIONS:
+               return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts);
+       case SR_CONF_MEASURED_QUANTITY:
+               /* TODO Use std_gvar_measured_quantities() when available. */
+               if (!devc)
+                       return SR_ERR_ARG;
+               g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY);
+               for (i = 0; i < devc->model->mqopt_size; i++) {
+                       arr[0] = g_variant_new_uint32(devc->model->mqopts[i].mq);
+                       arr[1] = g_variant_new_uint64(devc->model->mqopts[i].mqflag);
+                       gvar = g_variant_new_tuple(arr, ARRAY_SIZE(arr));
+                       g_variant_builder_add_value(&gvb, gvar);
+               }
+               *data = g_variant_builder_end(&gvb);
+               return SR_OK;
        default:
+               (void)devc;
                return SR_ERR_NA;
        }
-
-       return ret;
 }
 
 static int dev_acquisition_start(const struct sr_dev_inst *sdi)
 {
-       /* TODO: configure hardware, reset acquisition state, set up
-        * callbacks and send header packet. */
+       struct sr_scpi_dev_inst *scpi;
+       struct dev_context *devc;
+       int ret;
+       const char *command;
+
+       scpi = sdi->conn;
+       devc = sdi->priv;
+
+       ret = scpi_dmm_get_mq(sdi, &devc->start_acq_mq.curr_mq,
+               &devc->start_acq_mq.curr_mqflag, NULL);
+       if (ret != SR_OK)
+               return ret;
+
+       command = sr_scpi_cmd_get(devc->cmdset, DMM_CMD_START_ACQ);
+       if (command && *command) {
+               ret = sr_scpi_send(scpi, command);
+               scpi_dmm_cmd_delay(scpi);
+               if (ret != SR_OK)
+                       return ret;
+       }
 
-       (void)sdi;
+       sr_sw_limits_acquisition_start(&devc->limits);
+       ret = std_session_send_df_header(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       ret = sr_scpi_source_add(sdi->session, scpi, G_IO_IN, 10,
+               scpi_dmm_receive_data, (void *)sdi);
+       if (ret != SR_OK)
+               return ret;
 
        return SR_OK;
 }
 
 static int dev_acquisition_stop(struct sr_dev_inst *sdi)
 {
-       /* TODO: stop acquisition. */
+       struct sr_scpi_dev_inst *scpi;
+       struct dev_context *devc;
+       const char *command;
+
+       scpi = sdi->conn;
+       devc = sdi->priv;
 
-       (void)sdi;
+       command = sr_scpi_cmd_get(devc->cmdset, DMM_CMD_STOP_ACQ);
+       if (command && *command) {
+               (void)sr_scpi_send(scpi, command);
+               scpi_dmm_cmd_delay(scpi);
+       }
+       sr_scpi_source_remove(sdi->session, scpi);
+
+       std_session_send_df_end(sdi);
 
        return SR_OK;
 }
@@ -149,5 +338,4 @@ SR_PRIV struct sr_dev_driver scpi_dmm_driver_info = {
        .dev_acquisition_stop = dev_acquisition_stop,
        .context = NULL,
 };
-
 SR_REGISTER_DEV_DRIVER(scpi_dmm_driver_info);
index ec50c8be1151e9b1df9908bf3b696a0c93e4f1a5..08c79b4a19be39bcffaf619b5e10c8c2e8ed5dbf 100644 (file)
  */
 
 #include <config.h>
+#include <math.h>
+#include <string.h>
 #include "protocol.h"
 
+#define WITH_CMD_DELAY 0       /* TODO See which devices need delays. */
+
+SR_PRIV void scpi_dmm_cmd_delay(struct sr_scpi_dev_inst *scpi)
+{
+       if (WITH_CMD_DELAY)
+               g_usleep(WITH_CMD_DELAY * 1000);
+       sr_scpi_get_opc(scpi);
+}
+
+SR_PRIV const struct mqopt_item *scpi_dmm_lookup_mq_number(
+       const struct sr_dev_inst *sdi, enum sr_mq mq, enum sr_mqflag flag)
+{
+       struct dev_context *devc;
+       size_t i;
+       const struct mqopt_item *item;
+
+       devc = sdi->priv;
+       for (i = 0; i < devc->model->mqopt_size; i++) {
+               item = &devc->model->mqopts[i];
+               if (item->mq != mq || item->mqflag != flag)
+                       continue;
+               return item;
+       }
+
+       return NULL;
+}
+
+SR_PRIV const struct mqopt_item *scpi_dmm_lookup_mq_text(
+       const struct sr_dev_inst *sdi, const char *text)
+{
+       struct dev_context *devc;
+       size_t i;
+       const struct mqopt_item *item;
+
+       devc = sdi->priv;
+       for (i = 0; i < devc->model->mqopt_size; i++) {
+               item = &devc->model->mqopts[i];
+               if (!item->scpi_func_query || !item->scpi_func_query[0])
+                       continue;
+               if (!g_str_has_prefix(text, item->scpi_func_query))
+                       continue;
+               return item;
+       }
+
+       return NULL;
+}
+
+SR_PRIV int scpi_dmm_get_mq(const struct sr_dev_inst *sdi,
+       enum sr_mq *mq, enum sr_mqflag *flag, char **rsp)
+{
+       struct dev_context *devc;
+       const char *command;
+       char *response;
+       const char *have;
+       int ret;
+       const struct mqopt_item *item;
+
+       devc = sdi->priv;
+       if (mq)
+               *mq = 0;
+       if (flag)
+               *flag = 0;
+       if (rsp)
+               *rsp = NULL;
+
+       command = sr_scpi_cmd_get(devc->cmdset, DMM_CMD_QUERY_FUNC);
+       if (!command || !*command)
+               return SR_ERR_NA;
+       response = NULL;
+       ret = sr_scpi_get_string(sdi->conn, command, &response);
+       scpi_dmm_cmd_delay(sdi->conn);
+       if (ret != SR_OK)
+               return ret;
+       if (!response || !*response)
+               return SR_ERR_NA;
+       have = response;
+       if (*have == '"')
+               have++;
+
+       ret = SR_ERR_NA;
+       item = scpi_dmm_lookup_mq_text(sdi, have);
+       if (item) {
+               if (mq)
+                       *mq = item->mq;
+               if (flag)
+                       *flag = item->mqflag;
+               ret = SR_OK;
+       }
+
+       if (rsp) {
+               *rsp = response;
+               response = NULL;
+       }
+       g_free(response);
+
+       return ret;
+}
+
+SR_PRIV int scpi_dmm_set_mq(const struct sr_dev_inst *sdi,
+       enum sr_mq mq, enum sr_mqflag flag)
+{
+       struct dev_context *devc;
+       const struct mqopt_item *item;
+       const char *mode, *command;
+       int ret;
+
+       devc = sdi->priv;
+       item = scpi_dmm_lookup_mq_number(sdi, mq, flag);
+       if (!item)
+               return SR_ERR_NA;
+
+       mode = item->scpi_func_setup;
+       command = sr_scpi_cmd_get(devc->cmdset, DMM_CMD_SETUP_FUNC);
+       ret = sr_scpi_send(sdi->conn, command, mode);
+       scpi_dmm_cmd_delay(sdi->conn);
+
+       return ret;
+}
+
+SR_PRIV int scpi_dmm_get_meas_agilent(const struct sr_dev_inst *sdi, size_t ch)
+{
+       struct sr_scpi_dev_inst *scpi;
+       struct dev_context *devc;
+       struct scpi_dmm_acq_info *info;
+       struct sr_datafeed_analog *analog;
+       int ret;
+       enum sr_mq mq;
+       enum sr_mqflag mqflag;
+       char *mode_response;
+       const char *p;
+       char **fields;
+       size_t count;
+       char prec_text[20];
+       const struct mqopt_item *item;
+       int prec_exp;
+       const char *command;
+       char *response;
+       gboolean use_double;
+       int sig_digits, val_exp;
+       int digits;
+       enum sr_unit unit;
+
+       scpi = sdi->conn;
+       devc = sdi->priv;
+       info = &devc->run_acq_info;
+       analog = &info->analog[ch];
+
+       /*
+        * Get the meter's current mode, keep the response around.
+        * Skip the measurement if the mode is uncertain.
+        */
+       ret = scpi_dmm_get_mq(sdi, &mq, &mqflag, &mode_response);
+       if (ret != SR_OK) {
+               g_free(mode_response);
+               return ret;
+       }
+       if (!mode_response)
+               return SR_ERR;
+       if (!mq) {
+               g_free(mode_response);
+               return +1;
+       }
+
+       /*
+        * Get the last comma separated field of the function query
+        * response, or fallback to the model's default precision for
+        * the current function. This copes with either of these cases:
+        *   VOLT +1.00000E-01,+1.00000E-06
+        *   DIOD
+        *   TEMP THER,5000,+1.00000E+00,+1.00000E-01
+        */
+       p = sr_scpi_unquote_string(mode_response);
+       fields = g_strsplit(p, ",", 0);
+       count = g_strv_length(fields);
+       if (count >= 2) {
+               snprintf(prec_text, sizeof(prec_text),
+                       "%s", fields[count - 1]);
+               p = prec_text;
+       } else {
+               item = scpi_dmm_lookup_mq_number(sdi, mq, mqflag);
+               if (!item) {
+                       p = NULL;
+               } else if (item->default_precision == NO_DFLT_PREC) {
+                       p = NULL;
+               } else {
+                       snprintf(prec_text, sizeof(prec_text),
+                               "1e%d", item->default_precision);
+                       p = prec_text;
+               }
+       }
+       g_strfreev(fields);
+
+       /*
+        * Need to extract the exponent value ourselves, since a strtod()
+        * call will "eat" the exponent, too. Strip space, strip sign,
+        * strip float number (without! exponent), check for exponent
+        * and get exponent value. Accept absence of Esnn suffixes.
+        */
+       while (p && *p && g_ascii_isspace(*p))
+               p++;
+       if (p && *p && (*p == '+' || *p == '-'))
+               p++;
+       while (p && *p && g_ascii_isdigit(*p))
+               p++;
+       if (p && *p && *p == '.')
+               p++;
+       while (p && *p && g_ascii_isdigit(*p))
+               p++;
+       ret = SR_OK;
+       if (!p || !*p)
+               prec_exp = 0;
+       else if (*p != 'e' && *p != 'E')
+               ret = SR_ERR_DATA;
+       else
+               ret = sr_atoi(++p, &prec_exp);
+       g_free(mode_response);
+       if (ret != SR_OK)
+               return ret;
+
+       /*
+        * Get the measurement value. Make sure to strip trailing space
+        * or else number conversion may fail in fatal ways. Detect OL
+        * conditions. Determine the measurement's precision: Count the
+        * number of significant digits before the period, and get the
+        * exponent's value.
+        *
+        * The text presentation of values is like this:
+        *   +1.09450000E-01
+        * Skip space/sign, count digits before the period, skip to the
+        * exponent, get exponent value.
+        *
+        * TODO Can sr_parse_rational() return the exponent for us? In
+        * addition to providing a precise rational value instead of a
+        * float that's an approximation of the received value? Can the
+        * 'analog' struct that we fill in carry rationals?
+        *
+        * Use double precision FP here during conversion. Optionally
+        * downgrade to single precision later to reduce the amount of
+        * logged information.
+        */
+       command = sr_scpi_cmd_get(devc->cmdset, DMM_CMD_QUERY_VALUE);
+       if (!command || !*command)
+               return SR_ERR_NA;
+       ret = sr_scpi_get_string(scpi, command, &response);
+       scpi_dmm_cmd_delay(scpi);
+       if (ret != SR_OK)
+               return ret;
+       g_strstrip(response);
+       use_double = devc->model->digits > 6;
+       ret = sr_atod_ascii(response, &info->d_value);
+       if (ret != SR_OK) {
+               g_free(response);
+               return ret;
+       }
+       if (!response)
+               return SR_ERR;
+       if (info->d_value > +9e37) {
+               info->d_value = +INFINITY;
+       } else if (info->d_value < -9e37) {
+               info->d_value = -INFINITY;
+       } else {
+               p = response;
+               while (p && *p && g_ascii_isspace(*p))
+                       p++;
+               if (p && *p && (*p == '-' || *p == '+'))
+                       p++;
+               sig_digits = 0;
+               while (p && *p && g_ascii_isdigit(*p)) {
+                       sig_digits++;
+                       p++;
+               }
+               if (p && *p && *p == '.')
+                       p++;
+               while (p && *p && g_ascii_isdigit(*p))
+                       p++;
+               ret = SR_OK;
+               if (!p || !*p)
+                       val_exp = 0;
+               else if (*p != 'e' && *p != 'E')
+                       ret = SR_ERR_DATA;
+               else
+                       ret = sr_atoi(++p, &val_exp);
+       }
+       g_free(response);
+       if (ret != SR_OK)
+               return ret;
+       /*
+        * TODO Come up with the most appropriate 'digits' calculation.
+        * This implementation assumes that either the device provides
+        * the resolution with the query for the meter's function, or
+        * the driver uses a fallback text pretending the device had
+        * provided it. This works with supported Agilent devices.
+        *
+        * An alternative may be to assume a given digits count which
+        * depends on the device, and adjust that count based on the
+        * value's significant digits and exponent. But this approach
+        * fails if devices change their digits count depending on
+        * modes or user requests, and also fails when e.g. devices
+        * with "100000 counts" can provide values between 100000 and
+        * 120000 in either 4 or 5 digits modes, depending on the most
+        * recent trend of the values. This less robust approach should
+        * only be taken if the mode inquiry won't yield the resolution
+        * (as e.g. DIOD does on 34405A, though we happen to know the
+        * fixed resolution for this very mode on this very model).
+        *
+        * For now, let's keep the prepared code path for the second
+        * approach in place, should some Agilent devices need it yet
+        * benefit from re-using most of the remaining acquisition
+        * routine.
+        */
+#if 1
+       digits = -prec_exp;
+#else
+       digits = devc->model->digits;
+       digits -= sig_digits;
+       digits -= val_exp;
+#endif
+
+       /*
+        * Fill in the 'analog' description: value, encoding, meaning.
+        * Callers will fill in the sample count, and channel name,
+        * and will send out the packet.
+        */
+       if (use_double) {
+               analog->data = &info->d_value;
+               analog->encoding->unitsize = sizeof(info->d_value);
+       } else {
+               info->f_value = info->d_value;
+               analog->data = &info->f_value;
+               analog->encoding->unitsize = sizeof(info->f_value);
+       }
+       analog->encoding->is_float = TRUE;
+#ifdef WORDS_BIGENDIAN
+       analog->encoding->is_bigendian = TRUE;
+#else
+       analog->encoding->is_bigendian = FALSE;
+#endif
+       analog->encoding->digits = digits;
+       analog->meaning->mq = mq;
+       analog->meaning->mqflags = mqflag;
+       switch (mq) {
+       case SR_MQ_VOLTAGE:
+               unit = SR_UNIT_VOLT;
+               break;
+       case SR_MQ_CURRENT:
+               unit = SR_UNIT_AMPERE;
+               break;
+       case SR_MQ_RESISTANCE:
+       case SR_MQ_CONTINUITY:
+               unit = SR_UNIT_OHM;
+               break;
+       case SR_MQ_CAPACITANCE:
+               unit = SR_UNIT_FARAD;
+               break;
+       case SR_MQ_TEMPERATURE:
+               unit = SR_UNIT_CELSIUS;
+               break;
+       case SR_MQ_FREQUENCY:
+               unit = SR_UNIT_HERTZ;
+               break;
+       default:
+               return SR_ERR_NA;
+       }
+       analog->meaning->unit = unit;
+       analog->spec->spec_digits = digits;
+
+       return SR_OK;
+}
+
+/* Strictly speaking this is a timer controlled poll routine. */
 SR_PRIV int scpi_dmm_receive_data(int fd, int revents, void *cb_data)
 {
-       const struct sr_dev_inst *sdi;
+       struct sr_dev_inst *sdi;
+       struct sr_scpi_dev_inst *scpi;
        struct dev_context *devc;
+       struct scpi_dmm_acq_info *info;
+       gboolean sent_sample;
+       size_t ch;
+       struct sr_channel *channel;
+       int ret;
 
        (void)fd;
+       (void)revents;
 
-       if (!(sdi = cb_data))
+       sdi = cb_data;
+       if (!sdi)
                return TRUE;
-
-       if (!(devc = sdi->priv))
+       scpi = sdi->conn;
+       devc = sdi->priv;
+       if (!scpi || !devc)
                return TRUE;
+       info = &devc->run_acq_info;
+
+       sent_sample = FALSE;
+       ret = SR_OK;
+       for (ch = 0; ch < devc->num_channels; ch++) {
+               /* Check the channel's enabled status. */
+               channel = g_slist_nth_data(sdi->channels, ch);
+               if (!channel->enabled)
+                       continue;
+
+               /*
+                * Prepare an analog measurement value. Note that digits
+                * will get updated later.
+                */
+               info->packet.type = SR_DF_ANALOG;
+               info->packet.payload = &info->analog[ch];
+               sr_analog_init(&info->analog[ch], &info->encoding[ch],
+                       &info->meaning[ch], &info->spec[ch], 0);
+
+               /* Just check OPC before sending another request. */
+               scpi_dmm_cmd_delay(sdi->conn);
 
-       if (revents == G_IO_IN) {
-               /* TODO */
+               /*
+                * Have the model take and interpret a measurement. Lack
+                * of support is pointless, failed retrieval/conversion
+                * is considered fatal. The routine will fill in the
+                * 'analog' details, except for channel name and sample
+                * count (assume one value per channel).
+                *
+                * Note that non-zero non-negative return codes signal
+                * that the channel's data shell get skipped in this
+                * iteration over the channels. This copes with devices
+                * or modes where channels may provide data at different
+                * rates.
+                */
+               if (!devc->model->get_measurement) {
+                       ret = SR_ERR_NA;
+                       break;
+               }
+               ret = devc->model->get_measurement(sdi, ch);
+               if (ret > 0)
+                       continue;
+               if (ret != SR_OK)
+                       break;
+
+               /* Send the packet that was filled in by the model's routine. */
+               info->analog[ch].num_samples = 1;
+               info->analog[ch].meaning->channels = g_slist_append(NULL, channel);
+               sr_session_send(sdi, &info->packet);
+               g_slist_free(info->analog[ch].meaning->channels);
+               sent_sample = TRUE;
+       }
+       if (sent_sample)
+               sr_sw_limits_update_samples_read(&devc->limits, 1);
+       if (ret != SR_OK) {
+               /* Stop acquisition upon communication or data errors. */
+               sr_dev_acquisition_stop(sdi);
+               return TRUE;
        }
+       if (sr_sw_limits_check(&devc->limits))
+               sr_dev_acquisition_stop(sdi);
 
        return TRUE;
 }
index e672f33e7e9e46df1c198f2d2b371c41fc2cd464..4a569df58d8f4cf504d795cabc5b3cd3b6b3244e 100644 (file)
 #ifndef LIBSIGROK_HARDWARE_SCPI_DMM_PROTOCOL_H
 #define LIBSIGROK_HARDWARE_SCPI_DMM_PROTOCOL_H
 
-#include <stdint.h>
+#include <config.h>
 #include <glib.h>
+#include <stdint.h>
+#include <stdlib.h>
 #include <libsigrok/libsigrok.h>
 #include "libsigrok-internal.h"
+#include "scpi.h"
 
 #define LOG_PREFIX "scpi-dmm"
 
+#define SCPI_DMM_MAX_CHANNELS  1
+
+enum scpi_dmm_cmdcode {
+       DMM_CMD_SETUP_REMOTE,
+       DMM_CMD_SETUP_FUNC,
+       DMM_CMD_QUERY_FUNC,
+       DMM_CMD_START_ACQ,
+       DMM_CMD_STOP_ACQ,
+       DMM_CMD_QUERY_VALUE,
+       DMM_CMD_QUERY_PREC,
+};
+
+struct mqopt_item {
+       enum sr_mq mq;
+       enum sr_mqflag mqflag;
+       const char *scpi_func_setup;
+       const char *scpi_func_query;
+       int default_precision;
+};
+#define NO_DFLT_PREC   -99
+
+struct scpi_dmm_model {
+       const char *vendor;
+       const char *model;
+       size_t num_channels;
+       ssize_t digits;
+       const struct scpi_command *cmdset;
+       const struct mqopt_item *mqopts;
+       size_t mqopt_size;
+       int (*get_measurement)(const struct sr_dev_inst *sdi, size_t ch);
+};
+
 struct dev_context {
+       size_t num_channels;
+       const struct scpi_command *cmdset;
+       const struct scpi_dmm_model *model;
+       struct sr_sw_limits limits;
+       struct {
+               enum sr_mq curr_mq;
+               enum sr_mqflag curr_mqflag;
+       } start_acq_mq;
+       struct scpi_dmm_acq_info {
+               float f_value;
+               double d_value;
+               struct sr_datafeed_packet packet;
+               struct sr_datafeed_analog analog[SCPI_DMM_MAX_CHANNELS];
+               struct sr_analog_encoding encoding[SCPI_DMM_MAX_CHANNELS];
+               struct sr_analog_meaning meaning[SCPI_DMM_MAX_CHANNELS];
+               struct sr_analog_spec spec[SCPI_DMM_MAX_CHANNELS];
+       } run_acq_info;
 };
 
+SR_PRIV void scpi_dmm_cmd_delay(struct sr_scpi_dev_inst *scpi);
+SR_PRIV const struct mqopt_item *scpi_dmm_lookup_mq_number(
+       const struct sr_dev_inst *sdi, enum sr_mq mq, enum sr_mqflag flag);
+SR_PRIV const struct mqopt_item *scpi_dmm_lookup_mq_text(
+       const struct sr_dev_inst *sdi, const char *text);
+SR_PRIV int scpi_dmm_get_mq(const struct sr_dev_inst *sdi,
+       enum sr_mq *mq, enum sr_mqflag *flag, char **rsp);
+SR_PRIV int scpi_dmm_set_mq(const struct sr_dev_inst *sdi,
+       enum sr_mq mq, enum sr_mqflag flag);
+SR_PRIV int scpi_dmm_get_meas_agilent(const struct sr_dev_inst *sdi, size_t ch);
 SR_PRIV int scpi_dmm_receive_data(int fd, int revents, void *cb_data);
 
+SR_PRIV struct sr_dev_driver scpi_dmm_driver_info;
+
 #endif