From: poljar (Damir Jelić) Date: Tue, 29 Oct 2013 11:15:47 +0000 (+0100) Subject: scpi: Add helper functions for SCPI communication. X-Git-Tag: libsigrok-0.3.0~514 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=7b9d73203163daa70273f0adfa854a1dc01f08f0;p=libsigrok.git scpi: Add helper functions for SCPI communication. The Standard Commands for Programmable Instruments (SCPI) defines a standard for syntax and commands to use in controlling programmable test and measurement devices. SCPI documentation: http://www.ivifoundation.org/docs/scpi-99.pdf This patch adds helper functions for sending SCPI commands, reading a SCPI response and reading and parsing a SCPI "*IDN?" response. --- diff --git a/hardware/common/Makefile.am b/hardware/common/Makefile.am index de8f06c1..716436b7 100644 --- a/hardware/common/Makefile.am +++ b/hardware/common/Makefile.am @@ -25,7 +25,7 @@ noinst_LTLIBRARIES = libsigrok_hw_common.la libsigrok_hw_common_la_SOURCES = if NEED_SERIAL -libsigrok_hw_common_la_SOURCES += serial.c +libsigrok_hw_common_la_SOURCES += serial.c scpi.c endif if NEED_USB diff --git a/hardware/common/scpi.c b/hardware/common/scpi.c new file mode 100644 index 00000000..d8f5a2b1 --- /dev/null +++ b/hardware/common/scpi.c @@ -0,0 +1,219 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2013 poljar (Damir Jelić) + * + * 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 "libsigrok.h" +#include "libsigrok-internal.h" + +#include +#include + +/* Message logging helpers with subsystem-specific prefix string. */ +#define LOG_PREFIX "scpi: " +#define sr_log(l, s, args...) sr_log(l, LOG_PREFIX s, ## args) +#define sr_spew(s, args...) sr_spew(LOG_PREFIX s, ## args) +#define sr_dbg(s, args...) sr_dbg(LOG_PREFIX s, ## args) +#define sr_info(s, args...) sr_info(LOG_PREFIX s, ## args) +#define sr_warn(s, args...) sr_warn(LOG_PREFIX s, ## args) + +#define SCPI_READ_RETRIES 100 +#define SCPI_READ_RETRY_TIMEOUT 10000 + +/** + * Send a SCPI command. + * + * @param serial Previously initialized serial port structure. + * @param command The SCPI command to send to the device. + * + * @return SR_OK on success, SR_ERR on failure. + */ +SR_PRIV int sr_scpi_send(struct sr_serial_dev_inst *serial, + const char *command) +{ + int len; + int out; + gchar *terminated_command; + + terminated_command = g_strconcat(command, "\n", NULL); + len = strlen(terminated_command); + + out = serial_write(serial, terminated_command, + strlen(terminated_command)); + + g_free(terminated_command); + + if (out != len) { + sr_dbg("Only sent %d/%d bytes of SCPI command: '%s'.", out, + len, command); + return SR_ERR; + } + + sr_spew("Successfully sent SCPI command: '%s'.", command); + + return SR_OK; +} + +/** + * Send a SCPI command, receive the reply and store the reply in scpi_response. + * + * @param serial Previously initialized serial port structure. + * @param command The SCPI command to send to the device (can be NULL). + * @param scpi_response Pointer where to store the scpi response. + * + * @return SR_OK upon fetching a full SCPI response, SR_ERR upon fetching a + * incomplete or no response. The allocated response must be freed by the caller + * in the case of a full response as well in the case of an incomplete. + */ +SR_PRIV int sr_scpi_get_string(struct sr_serial_dev_inst *serial, + const char *command, char **scpi_response) +{ + int len; + int ret; + char buf[256]; + unsigned int i; + GString *response; + + if (command) + if (sr_scpi_send(serial, command) != SR_OK) + return SR_ERR; + + response = g_string_sized_new(1024); + + for (i = 0; i <= SCPI_READ_RETRIES; i++) { + while ((len = serial_read(serial, buf, sizeof(buf))) > 0) + response = g_string_append_len(response, buf, len); + + if (response->len > 0 && + response->str[response->len-1] == '\n') { + sr_spew("Fetched full SCPI response"); + break; + } + + g_usleep(SCPI_READ_RETRY_TIMEOUT); + } + + if (response->len == 0) { + sr_dbg("No SCPI response received"); + g_string_free(response, TRUE); + *scpi_response = NULL; + return SR_ERR; + + } else if (response->str[response->len-1] == '\n') { + /* + * The SCPI response contains a LF ('\n') at the end and we + * don't need this so replace it with a '\0' and decrement + * the length. + */ + response->str[--response->len] = '\0'; + ret = SR_OK; + + } else { + sr_warn("Incomplete SCPI response received!"); + ret = SR_ERR; + } + + /* Minor optimization: steal the string instead of copying. */ + *scpi_response = response->str; + + /* A SCPI response can be quite large, print at most 50 characters */ + sr_dbg("SCPI response for command %s received (length %d): '%.50s'", + command, response->len, response->str); + + g_string_free(response, FALSE); + + return ret; +} + +/** + * 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. + * + * @param serial Previously initialized serial port structure. + * @param scpi_response Pointer where to store the hw_info structure. + * + * @return SR_OK upon success, SR_ERR on failure. + * The hw_info structure must be freed by the caller with sr_scpi_hw_info_free(). + */ +SR_PRIV int sr_scpi_get_hw_id(struct sr_serial_dev_inst *serial, + struct sr_scpi_hw_info **scpi_response) +{ + int num_tokens; + char *response; + gchar **tokens; + + struct sr_scpi_hw_info *hw_info; + + response = NULL; + tokens = NULL; + + if (sr_scpi_get_string(serial, SCPI_CMD_IDN, &response) != SR_OK) + if (!response) + return SR_ERR; + + /* + * The response to a '*IDN?' is specified by the SCPI spec. It contains + * a comma-separated list containing the manufacturer name, instrument + * model, serial number of the instrument and the firmware version. + */ + tokens = g_strsplit(response, ",", 0); + + for (num_tokens = 0; tokens[num_tokens] != NULL; num_tokens++); + + if (num_tokens != 4) { + sr_dbg("IDN response not according to spec: %80.s", response); + g_strfreev(tokens); + g_free(response); + return SR_ERR; + } + g_free(response); + + hw_info = g_try_malloc(sizeof(struct sr_scpi_hw_info)); + if (!hw_info) { + g_strfreev(tokens); + return SR_ERR_MALLOC; + } + + hw_info->manufacturer = g_strdup(tokens[0]); + hw_info->model = g_strdup(tokens[1]); + hw_info->serial_number = g_strdup(tokens[2]); + hw_info->firmware_version = g_strdup(tokens[3]); + + g_strfreev(tokens); + + *scpi_response = hw_info; + + return SR_OK; +} + +/** + * Free a sr_scpi_hw_info struct. + * + * @param hw_info Pointer to the struct to free. + * + * This function is safe to call with a NULL pointer. + */ +SR_PRIV void sr_scpi_hw_info_free(struct sr_scpi_hw_info *hw_info) +{ + if (hw_info) { + g_free(hw_info->manufacturer); + g_free(hw_info->model); + g_free(hw_info->serial_number); + g_free(hw_info->firmware_version); + g_free(hw_info); + } +} diff --git a/libsigrok-internal.h b/libsigrok-internal.h index 358d5559..be0114d6 100644 --- a/libsigrok-internal.h +++ b/libsigrok-internal.h @@ -238,6 +238,54 @@ SR_PRIV GSList *sr_usb_find(libusb_context *usb_ctx, const char *conn); SR_PRIV int sr_usb_open(libusb_context *usb_ctx, struct sr_usb_dev_inst *usb); #endif +/*--- hardware/common/scpi.c ------------------------------------------------*/ + +#ifdef HAVE_LIBSERIALPORT + +#define SCPI_CMD_IDN "*IDN?" +#define SCPI_CMD_OPC "*OPC?" + +enum { + SCPI_CMD_SET_TRIGGER_SOURCE, + SCPI_CMD_SET_TIMEBASE, + SCPI_CMD_SET_VERTICAL_DIV, + SCPI_CMD_SET_TRIGGER_SLOPE, + SCPI_CMD_SET_COUPLING, + SCPI_CMD_SET_HORIZ_TRIGGERPOS, + SCPI_CMD_GET_ANALOG_CHAN_STATE, + SCPI_CMD_GET_DIG_CHAN_STATE, + SCPI_CMD_GET_TIMEBASE, + SCPI_CMD_GET_VERTICAL_DIV, + SCPI_CMD_GET_VERTICAL_OFFSET, + SCPI_CMD_GET_TRIGGER_SOURCE, + SCPI_CMD_GET_HORIZ_TRIGGERPOS, + SCPI_CMD_GET_TRIGGER_SLOPE, + SCPI_CMD_GET_COUPLING, + SCPI_CMD_SET_ANALOG_CHAN_STATE, + SCPI_CMD_SET_DIG_CHAN_STATE, + SCPI_CMD_GET_DIG_POD_STATE, + SCPI_CMD_SET_DIG_POD_STATE, + SCPI_CMD_GET_ANALOG_DATA, + SCPI_CMD_GET_DIG_DATA, +}; + +struct sr_scpi_hw_info { + char *manufacturer; + char *model; + char *serial_number; + char *firmware_version; +}; + +SR_PRIV int sr_scpi_send(struct sr_serial_dev_inst *serial, + const char *command); +SR_PRIV int sr_scpi_get_string(struct sr_serial_dev_inst *serial, + const char *command, char **scpi_response); +SR_PRIV int sr_scpi_get_hw_id(struct sr_serial_dev_inst *serial, + struct sr_scpi_hw_info **scpi_reponse); +SR_PRIV void sr_scpi_hw_info_free(struct sr_scpi_hw_info *hw_info); + +#endif + /*--- hardware/common/dmm/es51922.c -----------------------------------------*/ #define ES51922_PACKET_SIZE 14