From: poljar (D. J. <po...@po...> - 2013-11-20 21:21:34
|
Hi. This patch set consists of two parts, adding common functions for SCPI based communications and adding support for Hameg HMO scopes. The SCPI functions currently only work for serial devices, and not all known SCPI formats have their helper function (e.g. "Block data" helper functions are missing). poljar (Damir Jelić) (10): struitl: Add helper functions: string to number. scpi: Add helper functions for SCPI communication. scpi: Add function to strictly parse bool strings. scpi: Add more functions (getting int/bool/float/double). scpi: Add a function to read and wait on a *OPC? reply. scpi: Add function to get an array of floats. scpi: Add function to fetch uint8_t. serial: Add function to extract serial options. hameg-hmo: Initial driver skeleton. hameg-hmo: Add initial working driver version. configure.ac | 11 + hardware/Makefile.am | 5 + hardware/common/Makefile.am | 2 +- hardware/common/scpi.c | 530 +++++++++++++++++++++++ hardware/common/serial.c | 42 ++ hardware/hameg-hmo/Makefile.am | 33 ++ hardware/hameg-hmo/api.c | 936 +++++++++++++++++++++++++++++++++++++++++ hardware/hameg-hmo/protocol.c | 718 +++++++++++++++++++++++++++++++ hardware/hameg-hmo/protocol.h | 132 ++++++ hwdriver.c | 6 + libsigrok-internal.h | 63 +++ proto.h | 4 + strutil.c | 121 ++++++ 13 files changed, 2602 insertions(+), 1 deletion(-) create mode 100644 hardware/common/scpi.c create mode 100644 hardware/hameg-hmo/Makefile.am create mode 100644 hardware/hameg-hmo/api.c create mode 100644 hardware/hameg-hmo/protocol.c create mode 100644 hardware/hameg-hmo/protocol.h -- 1.8.4.2 |
From: poljar (D. J. <po...@po...> - 2013-11-20 21:21:31
|
From: poljar (Damir Jelić) <pol...@gm...> This patch adds helper functions for converting a string to different number formats (double, long, float, int). This functions are exposed in the public API. --- proto.h | 4 +++ strutil.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/proto.h b/proto.h index 7ec6ae4..5b516ec 100644 --- a/proto.h +++ b/proto.h @@ -141,6 +141,10 @@ SR_API uint64_t sr_parse_timestring(const char *timestring); SR_API gboolean sr_parse_boolstring(const char *boolstring); SR_API int sr_parse_period(const char *periodstr, uint64_t *p, uint64_t *q); SR_API int sr_parse_voltage(const char *voltstr, uint64_t *p, uint64_t *q); +SR_API int sr_atol(const char *str, long *ret); +SR_API int sr_atoi(const char *str, int *ret); +SR_API int sr_atod(const char *str, double *ret); +SR_API int sr_atof(const char *str, float *ret); /*--- version.c -------------------------------------------------------------*/ diff --git a/strutil.c b/strutil.c index f6cdb73..6c65384 100644 --- a/strutil.c +++ b/strutil.c @@ -21,6 +21,7 @@ #include <stdint.h> #include <stdlib.h> #include <string.h> +#include <errno.h> #include "libsigrok.h" #include "libsigrok-internal.h" @@ -48,6 +49,126 @@ */ /** + * Convert a string representation of a numeric value to a long integer. The + * conversion is strict and will fail if the complete string does not represent + * a valid long integer. The function sets errno according to the details of the + * failure. + * + * @param str The string representation to convert. + * @param ret Pointer to long where the result of the conversion will be stored. + * + * @return SR_OK if conversion is successful, otherwise SR_ERR. + * + * @since 0.3.0 + */ +SR_API int sr_atol(const char *str, long *ret) +{ + long tmp; + char *endptr = NULL; + + errno = 0; + tmp = strtol(str, &endptr, 0); + + if (!endptr || *endptr || errno) { + if (!errno) + errno = EINVAL; + return SR_ERR; + } + + *ret = tmp; + return SR_OK; +} + +/** + * Convert a string representation of a numeric value to an integer. The + * conversion is strict and will fail if the complete string does not represent + * a valid integer. The function sets errno according to the details of the + * failure. + * + * @param str The string representation to convert. + * @param ret Pointer to int where the result of the conversion will be stored. + * + * @return SR_OK if conversion is successful, otherwise SR_ERR. + * + * @since 0.3.0 + */ +SR_API int sr_atoi(const char *str, int *ret) +{ + long tmp; + + if (sr_atol(str, &tmp) != SR_OK) + return SR_ERR; + + if ((int) tmp != tmp) { + errno = ERANGE; + return SR_ERR; + } + + *ret = (int) tmp; + return SR_OK; +} + +/** + * Convert a string representation of a numeric value to a double. The + * conversion is strict and will fail if the complete string does not represent + * a valid double. The function sets errno according to the details of the + * failure. + * + * @param str The string representation to convert. + * @param ret Pointer to double where the result of the conversion will be stored. + * + * @return SR_OK if conversion is successful, otherwise SR_ERR. + * + * @since 0.3.0 + */ +SR_API int sr_atod(const char *str, double *ret) +{ + double tmp; + char *endptr = NULL; + + errno = 0; + tmp = strtof(str, &endptr); + + if (!endptr || *endptr || errno) { + if (!errno) + errno = EINVAL; + return SR_ERR; + } + + *ret = tmp; + return SR_OK; +} + +/** + * Convert a string representation of a numeric value to a float. The + * conversion is strict and will fail if the complete string does not represent + * a valid float. The function sets errno according to the details of the + * failure. + * + * @param str The string representation to convert. + * @param ret Pointer to float where the result of the conversion will be stored. + * + * @return SR_OK if conversion is successful, otherwise SR_ERR. + * + * @since 0.3.0 + */ +SR_API int sr_atof(const char *str, float *ret) +{ + double tmp; + + if (sr_atod(str, &tmp) != SR_OK) + return SR_ERR; + + if ((float) tmp != tmp) { + errno = ERANGE; + return SR_ERR; + } + + *ret = (float) tmp; + return SR_OK; +} + +/** * Convert a numeric value value to its "natural" string representation * in SI units. * -- 1.8.4.2 |
From: poljar (D. J. <po...@po...> - 2013-11-20 21:21:33
|
From: poljar (Damir Jelić) <pol...@gm...> 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. --- hardware/common/Makefile.am | 2 +- hardware/common/scpi.c | 219 ++++++++++++++++++++++++++++++++++++++++++++ libsigrok-internal.h | 48 ++++++++++ 3 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 hardware/common/scpi.c diff --git a/hardware/common/Makefile.am b/hardware/common/Makefile.am index de8f06c..716436b 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 0000000..d8f5a2b --- /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ć) <pol...@gm...> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "libsigrok.h" +#include "libsigrok-internal.h" + +#include <glib.h> +#include <string.h> + +/* 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 c418c4e..6471c81 100644 --- a/libsigrok-internal.h +++ b/libsigrok-internal.h @@ -200,6 +200,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 -- 1.8.4.2 |
From: poljar (D. J. <po...@po...> - 2013-11-20 21:21:34
|
From: poljar (Damir Jelić) <pol...@gm...> This patch adds helper functions to read an SCPI response and parse the response as an integer, boolean, floating-point or double-precision floating-point number. --- hardware/common/scpi.c | 128 +++++++++++++++++++++++++++++++++++++++++++++++++ libsigrok-internal.h | 8 ++++ 2 files changed, 136 insertions(+) diff --git a/hardware/common/scpi.c b/hardware/common/scpi.c index f932854..03eea3e 100644 --- a/hardware/common/scpi.c +++ b/hardware/common/scpi.c @@ -180,6 +180,134 @@ SR_PRIV int sr_scpi_get_string(struct sr_serial_dev_inst *serial, } /** + * Send a SCPI command, read the reply, parse it as a bool value and store the + * result 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 parsed result. + * + * @return SR_OK on success, SR_ERR on failure. + */ +SR_PRIV int sr_scpi_get_bool(struct sr_serial_dev_inst *serial, + const char *command, gboolean *scpi_response) +{ + int ret; + char *response; + + response = NULL; + + if (sr_scpi_get_string(serial, command, &response) != SR_OK) + if (!response) + return SR_ERR; + + if (sr_parse_strict_bool(response, scpi_response) == SR_OK) + ret = SR_OK; + else + ret = SR_ERR; + + g_free(response); + + return ret; +} + +/** + * Send a SCPI command, read the reply, parse it as an integer and store the + * result 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 parsed result. + * + * @return SR_OK on success, SR_ERR on failure. + */ +SR_PRIV int sr_scpi_get_int(struct sr_serial_dev_inst *serial, + const char *command, int *scpi_response) +{ + int ret; + char *response; + + response = NULL; + + if (sr_scpi_get_string(serial, command, &response) != SR_OK) + if (!response) + return SR_ERR; + + if (sr_atoi(response, scpi_response) == SR_OK) + ret = SR_OK; + else + ret = SR_ERR; + + g_free(response); + + return ret; +} + +/** + * Send a SCPI command, read the reply, parse it as a float and store the + * result 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 parsed result. + * + * @return SR_OK on success, SR_ERR on failure. + */ +SR_PRIV int sr_scpi_get_float(struct sr_serial_dev_inst *serial, + const char *command, float *scpi_response) +{ + int ret; + char *response; + + response = NULL; + + if (sr_scpi_get_string(serial, command, &response) != SR_OK) + if (!response) + return SR_ERR; + + if (sr_atof(response, scpi_response) == SR_OK) + ret = SR_OK; + else + ret = SR_ERR; + + g_free(response); + + return ret; +} + +/** + * Send a SCPI command, read the reply, parse it as a double and store the + * result 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 parsed result. + * + * @return SR_OK on success, SR_ERR on failure. + */ +SR_PRIV int sr_scpi_get_double(struct sr_serial_dev_inst *serial, + const char *command, double *scpi_response) +{ + int ret; + char *response; + + response = NULL; + + if (sr_scpi_get_string(serial, command, &response) != SR_OK) + if (!response) + return SR_ERR; + + if (sr_atod(response, scpi_response) == SR_OK) + ret = SR_OK; + else + ret = SR_ERR; + + g_free(response); + + 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. * diff --git a/libsigrok-internal.h b/libsigrok-internal.h index 6471c81..a509cd7 100644 --- a/libsigrok-internal.h +++ b/libsigrok-internal.h @@ -242,6 +242,14 @@ 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_bool(struct sr_serial_dev_inst *serial, + const char *command, gboolean *scpi_response); +SR_PRIV int sr_scpi_get_int(struct sr_serial_dev_inst *serial, + const char *command, int *scpi_response); +SR_PRIV int sr_scpi_get_float(struct sr_serial_dev_inst *serial, + const char *command, float *scpi_response); +SR_PRIV int sr_scpi_get_double(struct sr_serial_dev_inst *serial, + const char *command, double *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); -- 1.8.4.2 |
From: poljar (D. J. <po...@po...> - 2013-11-20 21:21:33
|
From: poljar (Damir Jelić) <pol...@gm...> This patch adds a function that is similar to sr_parse_boolstring but its matching rules are more strict. --- hardware/common/scpi.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/hardware/common/scpi.c b/hardware/common/scpi.c index d8f5a2b..f932854 100644 --- a/hardware/common/scpi.c +++ b/hardware/common/scpi.c @@ -35,6 +35,46 @@ #define SCPI_READ_RETRY_TIMEOUT 10000 /** + * Parse a string representation of a boolean-like value into a gboolean. + * Similar to sr_parse_boolstring but rejects strings which do not represent + * a boolean-like value. + * + * @param str String to convert. + * @param ret Pointer to a gboolean where the result of the conversion will be + * stored. + * + * @return SR_OK on success, SR_ERR on failure. + */ +static int sr_parse_strict_bool(const char *str, gboolean *ret) +{ + if (!str) + return SR_ERR_ARG; + + if (!g_strcmp0(str, "1") || + !g_ascii_strncasecmp(str, "y", 1) || + !g_ascii_strncasecmp(str, "t", 1) || + !g_ascii_strncasecmp(str, "yes", 3) || + !g_ascii_strncasecmp(str, "true", 4) || + !g_ascii_strncasecmp(str, "on", 2)) { + + *ret = TRUE; + return SR_OK; + + } else if (!g_strcmp0(str, "0") || + !g_ascii_strncasecmp(str, "n", 1) || + !g_ascii_strncasecmp(str, "f", 1) || + !g_ascii_strncasecmp(str, "no", 2) || + !g_ascii_strncasecmp(str, "false", 5) || + !g_ascii_strncasecmp(str, "off", 3)) { + + *ret = FALSE; + return SR_OK; + } + + return SR_ERR; +} + +/** * Send a SCPI command. * * @param serial Previously initialized serial port structure. -- 1.8.4.2 |
From: poljar (D. J. <po...@po...> - 2013-11-20 21:21:38
|
The SCPI standard specifies the "*OPC?" command (Operation complete query) which queries the instrument for its operative state. When all pending operations are complete, the instrument responds with a "1". Some manufacturers block before completing all operations and don't respond with anything and some of them respond with a "0". This function handles both cases uniformly. --- hardware/common/scpi.c | 25 +++++++++++++++++++++++++ libsigrok-internal.h | 1 + 2 files changed, 26 insertions(+) diff --git a/hardware/common/scpi.c b/hardware/common/scpi.c index 03eea3e..b29e14b 100644 --- a/hardware/common/scpi.c +++ b/hardware/common/scpi.c @@ -308,6 +308,31 @@ SR_PRIV int sr_scpi_get_double(struct sr_serial_dev_inst *serial, } /** + * Send a SCPI *OPC? command, read the reply and return the result of the + * command. + * + * @param serial Previously initialized serial port structure. + * + * @return SR_OK on success, SR_ERR on failure. + */ +SR_PRIV int sr_scpi_get_opc(struct sr_serial_dev_inst *serial) +{ + unsigned int i; + gboolean opc; + + for (i = 0; i < SCPI_READ_RETRIES; ++i) { + sr_scpi_get_bool(serial, SCPI_CMD_OPC, &opc); + + if (opc) + return SR_OK; + + g_usleep(SCPI_READ_RETRY_TIMEOUT); + } + + return SR_ERR; +} + +/** * 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. * diff --git a/libsigrok-internal.h b/libsigrok-internal.h index a509cd7..6ec9502 100644 --- a/libsigrok-internal.h +++ b/libsigrok-internal.h @@ -250,6 +250,7 @@ SR_PRIV int sr_scpi_get_float(struct sr_serial_dev_inst *serial, const char *command, float *scpi_response); SR_PRIV int sr_scpi_get_double(struct sr_serial_dev_inst *serial, const char *command, double *scpi_response); +SR_PRIV int sr_scpi_get_opc(struct sr_serial_dev_inst *serial); 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); -- 1.8.4.2 |
From: poljar (D. J. <po...@po...> - 2013-11-20 21:21:38
|
From: poljar (Damir Jelić) <pol...@gm...> This patch adds a function to read and parse a SCPI response which contains a comma-separated list of floating-point numbers (e.g. "1.0e-5,2.0e-4,3.0e-3"). This is particularly useful if the instrument sends analog measurement data in this format. --- hardware/common/scpi.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ libsigrok-internal.h | 2 ++ 2 files changed, 61 insertions(+) diff --git a/hardware/common/scpi.c b/hardware/common/scpi.c index b29e14b..d9b8def 100644 --- a/hardware/common/scpi.c +++ b/hardware/common/scpi.c @@ -333,6 +333,65 @@ SR_PRIV int sr_scpi_get_opc(struct sr_serial_dev_inst *serial) } /** + * Send a SCPI command, read the reply, parse it as comma separated list of + * floats and store the as an result 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 parsed result. + * + * @return SR_OK upon successfully parsing all values, SR_ERR upon a parsing + * error or upon no response. The allocated response must be freed by the caller + * in the case of an SR_OK as well as in the case of parsing error. + */ +SR_PRIV int sr_scpi_get_floatv(struct sr_serial_dev_inst *serial, + const char *command, GArray **scpi_response) +{ + int ret; + float tmp; + char *response; + + gchar **ptr; + gchar **tokens; + GArray *response_array; + + ret = SR_OK; + response = NULL; + tokens = NULL; + + if (sr_scpi_get_string(serial, command, &response) != SR_OK) + if (!response) + return SR_ERR; + + tokens = g_strsplit(response, ",", 0); + ptr = tokens; + + response_array = g_array_sized_new(TRUE, FALSE, sizeof(float), 256); + + while(*ptr) { + if (sr_atof(*ptr, &tmp) == SR_OK) + response_array = g_array_append_val(response_array, + tmp); + else + ret = SR_ERR; + + ptr++; + } + g_strfreev(tokens); + g_free(response); + + if (ret == SR_ERR && response_array->len == 0) { + g_array_free(response_array, TRUE); + *scpi_response = NULL; + return SR_ERR; + } + + *scpi_response = response_array; + + 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. * diff --git a/libsigrok-internal.h b/libsigrok-internal.h index 6ec9502..f07390d 100644 --- a/libsigrok-internal.h +++ b/libsigrok-internal.h @@ -251,6 +251,8 @@ SR_PRIV int sr_scpi_get_float(struct sr_serial_dev_inst *serial, SR_PRIV int sr_scpi_get_double(struct sr_serial_dev_inst *serial, const char *command, double *scpi_response); SR_PRIV int sr_scpi_get_opc(struct sr_serial_dev_inst *serial); +SR_PRIV int sr_scpi_get_floatv(struct sr_serial_dev_inst *serial, + const char *command, GArray **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); -- 1.8.4.2 |
From: poljar (D. J. <po...@po...> - 2013-11-20 21:21:39
|
This patch adds a function to read and parse a SCPI response which contains a comma separated list of unsignet 8-bit integer numbers (e.g "1,0,64"). This is particularly useful if the instrument sends digital measurement data in this format. --- hardware/common/scpi.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ libsigrok-internal.h | 2 ++ 2 files changed, 61 insertions(+) diff --git a/hardware/common/scpi.c b/hardware/common/scpi.c index d9b8def..00cf518 100644 --- a/hardware/common/scpi.c +++ b/hardware/common/scpi.c @@ -392,6 +392,65 @@ SR_PRIV int sr_scpi_get_floatv(struct sr_serial_dev_inst *serial, } /** + * Send a SCPI command, read the reply, parse it as comma separated list of + * unsigned 8 bit integers and store the as an result 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 parsed result. + * + * @return SR_OK upon successfully parsing all values, SR_ERR upon a parsing + * error or upon no response. The allocated response must be freed by the caller + * in the case of an SR_OK as well as in the case of parsing error. + */ +SR_PRIV int sr_scpi_get_uint8v(struct sr_serial_dev_inst *serial, + const char *command, GArray **scpi_response) +{ + int tmp; + int ret; + char *response; + + gchar **ptr; + gchar **tokens; + GArray *response_array; + + ret = SR_OK; + response = NULL; + tokens = NULL; + + if (sr_scpi_get_string(serial, command, &response) != SR_OK) + if (!response) + return SR_ERR; + + tokens = g_strsplit(response, ",", 0); + ptr = tokens; + + response_array = g_array_sized_new(TRUE, FALSE, sizeof(uint8_t), 256); + + while(*ptr) { + if (sr_atoi(*ptr, &tmp) == SR_OK) + response_array = g_array_append_val(response_array, + tmp); + else + ret = SR_ERR; + + ptr++; + } + g_strfreev(tokens); + g_free(response); + + if (response_array->len == 0) { + g_array_free(response_array, TRUE); + *scpi_response = NULL; + return SR_ERR; + } + + *scpi_response = response_array; + + 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. * diff --git a/libsigrok-internal.h b/libsigrok-internal.h index f07390d..9425bf7 100644 --- a/libsigrok-internal.h +++ b/libsigrok-internal.h @@ -253,6 +253,8 @@ SR_PRIV int sr_scpi_get_double(struct sr_serial_dev_inst *serial, SR_PRIV int sr_scpi_get_opc(struct sr_serial_dev_inst *serial); SR_PRIV int sr_scpi_get_floatv(struct sr_serial_dev_inst *serial, const char *command, GArray **scpi_response); +SR_PRIV int sr_scpi_get_uint8v(struct sr_serial_dev_inst *serial, + const char *command, GArray **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); -- 1.8.4.2 |
From: poljar (D. J. <po...@po...> - 2013-11-20 21:21:39
|
From: poljar (Damir Jelić) <pol...@gm...> This patch adds a function for a common operation of all serial based drivers. It extracts the serial options from the options linked list that is passed down to every hardware driver. --- hardware/common/serial.c | 42 ++++++++++++++++++++++++++++++++++++++++++ libsigrok-internal.h | 2 ++ 2 files changed, 44 insertions(+) diff --git a/hardware/common/serial.c b/hardware/common/serial.c index de7f583..4751039 100644 --- a/hardware/common/serial.c +++ b/hardware/common/serial.c @@ -569,3 +569,45 @@ SR_PRIV int serial_stream_detect(struct sr_serial_dev_inst *serial, return SR_ERR; } + +/** + * Extract the serial device and options from the options linked list. + * + * @param options List of options passed from the command line. + * @param serial_device Pointer where to store the exctracted serial device. + * @param serial_options Pointer where to store the optional extracted serial + * options. + * + * @return SR_OK if a serial_device is found, SR_ERR if no device is found. The + * returned string should not be freed by the caller. + */ +SR_PRIV int sr_serial_extract_options(GSList *options, const char **serial_device, + const char **serial_options) +{ + GSList *l; + struct sr_config *src; + + *serial_device = NULL; + + for (l = options; l; l = l->next) { + src = l->data; + switch (src->key) { + case SR_CONF_CONN: + *serial_device = g_variant_get_string(src->data, NULL); + sr_dbg("Parsed serial device: %s", *serial_device); + break; + + case SR_CONF_SERIALCOMM: + *serial_options = g_variant_get_string(src->data, NULL); + sr_dbg("Parsed serial options: %s", *serial_options); + break; + } + } + + if (!*serial_device) { + sr_dbg("No serial device specified"); + return SR_ERR; + } + + return SR_OK; +} diff --git a/libsigrok-internal.h b/libsigrok-internal.h index 9425bf7..780394b 100644 --- a/libsigrok-internal.h +++ b/libsigrok-internal.h @@ -181,6 +181,8 @@ SR_PRIV int serial_stream_detect(struct sr_serial_dev_inst *serial, uint8_t *buf, size_t *buflen, size_t packet_size, packet_valid_t is_valid, uint64_t timeout_ms, int baudrate); +SR_PRIV int sr_serial_extract_options(GSList *options, const char **serial_device, + const char **serial_options); #endif /*--- hardware/common/ezusb.c -----------------------------------------------*/ -- 1.8.4.2 |
From: poljar (D. J. <po...@po...> - 2013-11-20 21:21:43
|
From: poljar (Damir Jelić) <pol...@gm...> --- configure.ac | 11 +++ hardware/Makefile.am | 5 ++ hardware/hameg-hmo/Makefile.am | 33 ++++++++ hardware/hameg-hmo/api.c | 185 +++++++++++++++++++++++++++++++++++++++++ hardware/hameg-hmo/protocol.c | 40 +++++++++ hardware/hameg-hmo/protocol.h | 51 ++++++++++++ hwdriver.c | 6 ++ 7 files changed, 331 insertions(+) create mode 100644 hardware/hameg-hmo/Makefile.am create mode 100644 hardware/hameg-hmo/api.c create mode 100644 hardware/hameg-hmo/protocol.c create mode 100644 hardware/hameg-hmo/protocol.h diff --git a/configure.ac b/configure.ac index 36a82d5..3388916 100644 --- a/configure.ac +++ b/configure.ac @@ -139,6 +139,15 @@ AC_ARG_ENABLE(fx2lafw, AC_HELP_STRING([--enable-fx2lafw], [HW_FX2LAFW="$enableval"], [HW_FX2LAFW=$HW_ENABLED_DEFAULT]) +AC_ARG_ENABLE(hameg-hmo, AC_HELP_STRING([--enable-hameg-hmo], + [enable Hameg HMO support [default=yes]]), + [HW_HAMEG_HMO="$enableval"], + [HW_HAMEG_HMO=$HW_ENABLED_DEFAULT]) +AM_CONDITIONAL(HW_HAMEG_HMO, test x$HW_HAMEG_HMO = xyes) +if test "x$HW_HAMEG_HMO" = "xyes"; then + AC_DEFINE(HAVE_HW_HAMEG_HMO, 1, [Hameg HMO support]) +fi + AC_ARG_ENABLE(hantek-dso, AC_HELP_STRING([--enable-hantek-dso], [enable Hantek DSO support [default=yes]]), [HW_HANTEK_DSO="$enableval"], @@ -537,6 +546,7 @@ AC_CONFIG_FILES([Makefile version.h hardware/Makefile hardware/chronovu-la8/Makefile hardware/colead-slm/Makefile hardware/common/Makefile + hardware/hameg-hmo/Makefile hardware/ikalogic-scanalogic2/Makefile hardware/ikalogic-scanaplus/Makefile hardware/kecheng-kc-330b/Makefile @@ -604,6 +614,7 @@ echo " - colead-slm...................... $HW_COLEAD_SLM" echo " - demo............................ $HW_DEMO" echo " - fluke-dmm....................... $HW_FLUKE_DMM" echo " - fx2lafw......................... $HW_FX2LAFW" +echo " - hameg-hmo....................... $HW_HAMEG_HMO" echo " - hantek-dso...................... $HW_HANTEK_DSO" echo " - ikalogic-scanalogic2............ $HW_IKALOGIC_SCANALOGIC2" echo " - ikalogic-scanaplus.............. $HW_IKALOGIC_SCANAPLUS" diff --git a/hardware/Makefile.am b/hardware/Makefile.am index 1f762da..f147695 100644 --- a/hardware/Makefile.am +++ b/hardware/Makefile.am @@ -31,6 +31,7 @@ SUBDIRS = \ demo \ fluke-dmm \ fx2lafw \ + hameg-hmo \ hantek-dso \ ikalogic-scanalogic2 \ ikalogic-scanaplus \ @@ -101,6 +102,10 @@ if HW_FX2LAFW libsigrokhardware_la_LIBADD += fx2lafw/libsigrok_hw_fx2lafw.la endif +if HW_HAMEG_HMO +libsigrokhardware_la_LIBADD += hameg-hmo/libsigrok_hw_hameg_hmo.la +endif + if HW_HANTEK_DSO libsigrokhardware_la_LIBADD += hantek-dso/libsigrok_hw_hantek_dso.la endif diff --git a/hardware/hameg-hmo/Makefile.am b/hardware/hameg-hmo/Makefile.am new file mode 100644 index 0000000..009060a --- /dev/null +++ b/hardware/hameg-hmo/Makefile.am @@ -0,0 +1,33 @@ +## +## This file is part of the libsigrok project. +## +## Copyright (C) 2013 poljar (Damir Jelić) <pol...@gm...> +## +## 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 <http://www.gnu.org/licenses/>. +## + +if HW_HAMEG_HMO + +# Local lib, this is NOT meant to be installed! +noinst_LTLIBRARIES = libsigrok_hw_hameg_hmo.la + +libsigrok_hw_hameg_hmo_la_SOURCES = \ + api.c \ + protocol.c \ + protocol.h + +libsigrok_hw_hameg_hmo_la_CFLAGS = \ + -I$(top_srcdir) + +endif diff --git a/hardware/hameg-hmo/api.c b/hardware/hameg-hmo/api.c new file mode 100644 index 0000000..8f76e65 --- /dev/null +++ b/hardware/hameg-hmo/api.c @@ -0,0 +1,185 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2013 poljar (Damir Jelić) <pol...@gm...> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "protocol.h" + +SR_PRIV struct sr_dev_driver hameg_hmo_driver_info; +static struct sr_dev_driver *di = &hameg_hmo_driver_info; + +static int init(struct sr_context *sr_ctx) +{ + return std_init(sr_ctx, di, LOG_PREFIX); +} + +static GSList *scan(GSList *options) +{ + struct drv_context *drvc; + GSList *devices; + + (void)options; + + devices = NULL; + drvc = di->priv; + drvc->instances = NULL; + + /* TODO: scan for devices, either based on a SR_CONF_CONN option + * or on a USB scan. */ + + return devices; +} + +static GSList *dev_list(void) +{ + return ((struct drv_context *)(di->priv))->instances; +} + +static int dev_clear(void) +{ + return std_dev_clear(di, NULL); +} + +static int dev_open(struct sr_dev_inst *sdi) +{ + (void)sdi; + + /* TODO: get handle from sdi->conn and open it. */ + + sdi->status = SR_ST_ACTIVE; + + return SR_OK; +} + +static int dev_close(struct sr_dev_inst *sdi) +{ + (void)sdi; + + /* TODO: get handle from sdi->conn and close it. */ + + sdi->status = SR_ST_INACTIVE; + + return SR_OK; +} + +static int cleanup(void) +{ + dev_clear(); + + /* TODO: free other driver resources, if any. */ + + return SR_OK; +} + +static int config_get(int key, GVariant **data, const struct sr_dev_inst *sdi) +{ + int ret; + + (void)sdi; + (void)data; + + ret = SR_OK; + switch (key) { + /* TODO */ + default: + return SR_ERR_NA; + } + + return ret; +} + +static int config_set(int key, GVariant *data, const struct sr_dev_inst *sdi) +{ + int ret; + + (void)data; + + if (sdi->status != SR_ST_ACTIVE) + return SR_ERR_DEV_CLOSED; + + ret = SR_OK; + switch (key) { + /* TODO */ + default: + ret = SR_ERR_NA; + } + + return ret; +} + +static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi) +{ + int ret; + + (void)sdi; + (void)data; + + ret = SR_OK; + switch (key) { + /* TODO */ + default: + return SR_ERR_NA; + } + + return ret; +} + +static int dev_acquisition_start(const struct sr_dev_inst *sdi, + void *cb_data) +{ + (void)sdi; + (void)cb_data; + + if (sdi->status != SR_ST_ACTIVE) + return SR_ERR_DEV_CLOSED; + + /* TODO: configure hardware, reset acquisition state, set up + * callbacks and send header packet. */ + + return SR_OK; +} + +static int dev_acquisition_stop(struct sr_dev_inst *sdi, void *cb_data) +{ + (void)cb_data; + + if (sdi->status != SR_ST_ACTIVE) + return SR_ERR_DEV_CLOSED; + + /* TODO: stop acquisition. */ + + return SR_OK; +} + +SR_PRIV struct sr_dev_driver hameg_hmo_driver_info = { + .name = "hameg-hmo", + .longname = "Hameg HMO scope driver", + .api_version = 1, + .init = init, + .cleanup = cleanup, + .scan = scan, + .dev_list = dev_list, + .dev_clear = 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, + .priv = NULL, +}; diff --git a/hardware/hameg-hmo/protocol.c b/hardware/hameg-hmo/protocol.c new file mode 100644 index 0000000..64b35e9 --- /dev/null +++ b/hardware/hameg-hmo/protocol.c @@ -0,0 +1,40 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2013 poljar (Damir Jelić) <pol...@gm...> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#include "protocol.h" + +SR_PRIV int hameg_hmo_receive_data(int fd, int revents, void *cb_data) +{ + const struct sr_dev_inst *sdi; + struct dev_context *devc; + + (void)fd; + + if (!(sdi = cb_data)) + return TRUE; + + if (!(devc = sdi->priv)) + return TRUE; + + if (revents == G_IO_IN) { + /* TODO */ + } + + return TRUE; +} diff --git a/hardware/hameg-hmo/protocol.h b/hardware/hameg-hmo/protocol.h new file mode 100644 index 0000000..0c06a93 --- /dev/null +++ b/hardware/hameg-hmo/protocol.h @@ -0,0 +1,51 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2013 poljar (Damir Jelić) <pol...@gm...> + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef LIBSIGROK_HARDWARE_HAMEG_HMO_PROTOCOL_H +#define LIBSIGROK_HARDWARE_HAMEG_HMO_PROTOCOL_H + +#include <stdint.h> +#include <glib.h> +#include "libsigrok.h" +#include "libsigrok-internal.h" + +/* Message logging helpers with subsystem-specific prefix string. */ +#define LOG_PREFIX "hameg-hmo: " +#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 sr_err(s, args...) sr_err(LOG_PREFIX s, ## args) + +/** Private, per-device-instance driver context. */ +struct dev_context { + /* Model-specific information */ + + /* Acquisition settings */ + + /* Operational state */ + + /* Temporary state across callbacks */ + +}; + +SR_PRIV int hameg_hmo_receive_data(int fd, int revents, void *cb_data); + +#endif diff --git a/hwdriver.c b/hwdriver.c index ade8b3d..3c4ee04 100644 --- a/hwdriver.c +++ b/hwdriver.c @@ -123,6 +123,9 @@ extern SR_PRIV struct sr_dev_driver colead_slm_driver_info; #ifdef HAVE_HW_DEMO extern SR_PRIV struct sr_dev_driver demo_driver_info; #endif +#ifdef HAVE_HW_HAMEG_HMO +extern SR_PRIV struct sr_dev_driver hameg_hmo_driver_info; +#endif #ifdef HAVE_HW_IKALOGIC_SCANALOGIC2 extern SR_PRIV struct sr_dev_driver ikalogic_scanalogic2_driver_info; #endif @@ -245,6 +248,9 @@ static struct sr_dev_driver *drivers_list[] = { #ifdef HAVE_HW_DEMO &demo_driver_info, #endif +#ifdef HAVE_HW_HAMEG_HMO + &hameg_hmo_driver_info, +#endif #ifdef HAVE_HW_IKALOGIC_SCANALOGIC2 &ikalogic_scanalogic2_driver_info, #endif -- 1.8.4.2 |
From: poljar (D. J. <po...@po...> - 2013-11-20 21:21:42
|
From: poljar (Damir Jelić) <pol...@gm...> This patch adds initial support for Hameg's HMO oscilloscopes. It currently supports only the HMO compact series (70MHz-200MHz). --- hardware/hameg-hmo/api.c | 831 ++++++++++++++++++++++++++++++++++++++++-- hardware/hameg-hmo/protocol.c | 682 +++++++++++++++++++++++++++++++++- hardware/hameg-hmo/protocol.h | 93 ++++- 3 files changed, 1558 insertions(+), 48 deletions(-) diff --git a/hardware/hameg-hmo/api.c b/hardware/hameg-hmo/api.c index 8f76e65..70d91a5 100644 --- a/hardware/hameg-hmo/api.c +++ b/hardware/hameg-hmo/api.c @@ -17,29 +17,246 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <stdlib.h> +#include <glib/gstdio.h> + #include "protocol.h" -SR_PRIV struct sr_dev_driver hameg_hmo_driver_info; -static struct sr_dev_driver *di = &hameg_hmo_driver_info; +#define SERIALCOMM "115200/8n1/flow=1" + +static const int32_t hwopts[] = { + SR_CONF_CONN, + SR_CONF_SERIALCOMM, +}; + +struct usb_id_info { + uint16_t vendor_id; + uint16_t product_id; +} usb_id_info; + +static struct usb_id_info ho_models[] = { + { + .vendor_id = 0x0403, + .product_id = 0xed72, /* HO720 */ + }, + { + .vendor_id = 0x0403, + .product_id = 0xed73, /* HO730 */ + }, +}; static int init(struct sr_context *sr_ctx) { return std_init(sr_ctx, di, LOG_PREFIX); } +/** + * Find USB serial devices via the USB vendor ID and product ID. + * + * @param vendor_id vendor ID of the USB device. + * @param product_id product ID of the USB device. + * + * @return A GSList of strings containing the path of the serial device or null + * if no serial device is found. The returned list must be freed by the caller. + */ +static GSList *auto_find_usb(unsigned long vendor_id, + unsigned long product_id) +{ +#ifdef __linux__ + const gchar *usb_dev; + const char device_tree[] = "/sys/bus/usb/devices/"; + + GDir *devices_dir; + + GSList *l = NULL; + GSList *tty_devices; + GSList *matched_paths; + + l = NULL; + tty_devices = NULL; + matched_paths = NULL; + + if (!(devices_dir = g_dir_open(device_tree, 0, NULL))) + return NULL; + + /* + * Find potential candidates using the vendor ID and product ID + * and store them in matched_paths + */ + while ((usb_dev = g_dir_read_name(devices_dir))) { + FILE *fd; + char tmp[5]; + + gchar *vendor_path; + gchar *product_path; + + unsigned long read_vendor_id; + unsigned long read_product_id; + + vendor_path = g_strconcat(device_tree, + usb_dev, "/idVendor", NULL); + product_path = g_strconcat(device_tree, + usb_dev, "/idProduct", NULL); + + if (!g_file_test(vendor_path, G_FILE_TEST_EXISTS) || + !g_file_test(product_path, G_FILE_TEST_EXISTS)) + goto skip_device; + + if ((fd = g_fopen(vendor_path, "r")) == NULL) + goto skip_device; + + if (fgets(tmp, sizeof(tmp), fd) == NULL) { + fclose(fd); + goto skip_device; + } + read_vendor_id = strtoul(tmp, NULL, 16); + + fclose(fd); + + if ((fd = g_fopen(product_path, "r")) == NULL) + goto skip_device; + + if (fgets(tmp, sizeof(tmp), fd) == NULL) { + fclose(fd); + goto skip_device; + } + read_product_id = strtoul(tmp, NULL, 16); + + fclose(fd); + + if (vendor_id == read_vendor_id && + product_id == read_product_id) { + gchar *path_copy; + + path_copy = g_strdup(usb_dev); + matched_paths = g_slist_prepend(matched_paths, + path_copy); + } + + skip_device: + g_free(vendor_path); + g_free(product_path); + } + g_dir_close(devices_dir); + + /* For every matched device try to find a ttyUSBX subfolder */ + for (l = matched_paths; l; l = l->next) { + const char *file; + + GDir *device_dir; + + gchar *prefix; + gchar *subdir_path; + gchar *device_path; + + subdir_path = NULL; + + device_path = g_strconcat(device_tree, l->data, NULL); + + if (!(device_dir = g_dir_open(device_path, 0, NULL))) { + g_free(device_path); + continue; + } + + prefix = g_strconcat(l->data, ":", NULL); + + while ((file = g_dir_read_name(device_dir))) { + if (g_str_has_prefix(file, prefix)) { + subdir_path = g_strconcat(device_path, + "/", file, + NULL); + break; + } + } + g_dir_close(device_dir); + + g_free(prefix); + g_free(device_path); + + if (subdir_path) { + if (!(device_dir = g_dir_open(subdir_path, 0, NULL))) { + g_free(subdir_path); + continue; + } + g_free(subdir_path); + + while ((file = g_dir_read_name(device_dir))) { + if (g_str_has_prefix(file, "ttyUSB")) { + gchar *tty_path; + + tty_path = g_strconcat("/dev/", + file, NULL); + sr_dbg("Found USB device %04x:%04x attached to %s", + vendor_id, product_id, tty_path); + tty_devices = g_slist_prepend(tty_devices, + tty_path); + break; + } + } + g_dir_close(device_dir); + } + } + g_slist_free_full(matched_paths, g_free); + + return tty_devices; +#else + return NULL; +#endif +} + static GSList *scan(GSList *options) { - struct drv_context *drvc; GSList *devices; - (void)options; + struct drv_context *drvc; + struct sr_dev_inst *sdi; + + const char *serial_device; + const char *serial_options; + serial_device = NULL; + serial_options = SERIALCOMM; + + sdi = NULL; devices = NULL; drvc = di->priv; drvc->instances = NULL; - /* TODO: scan for devices, either based on a SR_CONF_CONN option - * or on a USB scan. */ + if (sr_serial_extract_options(options, &serial_device, + &serial_options) == SR_OK) { + sdi = hameg_probe_serial_device(serial_device, serial_options); + + if (sdi != NULL) { + devices = g_slist_append(devices, sdi); + drvc->instances = g_slist_append(drvc->instances, sdi); + } + + } else { + GSList *l; + GSList *tty_devices; + + unsigned int i; + + tty_devices = NULL; + + for (i = 0; i < ARRAY_SIZE(ho_models); i++) { + if ((l = auto_find_usb(ho_models[i].vendor_id, + ho_models[i].product_id)) == NULL) + continue; + + tty_devices = g_slist_concat(tty_devices, l); + } + + for (l = tty_devices; l; l = l->next) { + sdi = hameg_probe_serial_device(l->data, serial_options); + if (sdi != NULL) { + devices = g_slist_append(devices, sdi); + drvc->instances = g_slist_append(drvc->instances, sdi); + } + } + + g_slist_free_full(tty_devices, g_free); + } return devices; } @@ -49,16 +266,46 @@ static GSList *dev_list(void) return ((struct drv_context *)(di->priv))->instances; } +static void clear_helper(void *priv) +{ + unsigned int i; + + struct dev_context *devc; + struct scope_config *model; + + devc = priv; + model = devc->model_config; + + scope_state_free(devc->model_state); + + for (i = 0; i < model->analog_channels; ++i) { + g_slist_free(devc->analog_groups[i].probes); + } + + for (i = 0; i < model->digital_pods; ++i) { + g_slist_free(devc->digital_groups[i].probes); + g_free(devc->digital_groups[i].name); + } + + g_free(devc->analog_groups); + g_free(devc->digital_groups); + + g_free(devc); +} + static int dev_clear(void) { - return std_dev_clear(di, NULL); + return std_dev_clear(di, clear_helper); } static int dev_open(struct sr_dev_inst *sdi) { - (void)sdi; + if (sdi->status != SR_ST_ACTIVE && + serial_open(sdi->conn, SERIAL_RDWR | SERIAL_NONBLOCK) != SR_OK) + return SR_ERR; - /* TODO: get handle from sdi->conn and open it. */ + if (scope_state_get(sdi) != SR_OK) + return SR_ERR; sdi->status = SR_ST_ACTIVE; @@ -67,9 +314,10 @@ static int dev_open(struct sr_dev_inst *sdi) static int dev_close(struct sr_dev_inst *sdi) { - (void)sdi; + if (sdi->status == SR_ST_INACTIVE) + return SR_OK; - /* TODO: get handle from sdi->conn and close it. */ + serial_close(sdi->conn); sdi->status = SR_ST_INACTIVE; @@ -80,87 +328,590 @@ static int cleanup(void) { dev_clear(); - /* TODO: free other driver resources, if any. */ - return SR_OK; } -static int config_get(int key, GVariant **data, const struct sr_dev_inst *sdi) +static int check_probe_group(struct dev_context *devc, + const struct sr_probe_group *probe_group) +{ + unsigned int i; + struct scope_config *model; + + model = devc->model_config; + + if (!probe_group) + return PG_NONE; + + for (i = 0; i < model->analog_channels; ++i) + if (probe_group == &devc->analog_groups[i]) + return PG_ANALOG; + + for (i = 0; i < model->digital_pods; ++i) + if (probe_group == &devc->digital_groups[i]) + return PG_DIGITAL; + + sr_err("Invalid probe group specified."); + return PG_INVALID; +} + +static int config_get(int key, GVariant **data, const struct sr_dev_inst *sdi, + const struct sr_probe_group *probe_group) { int ret; + int pg_type; + unsigned int i; + + struct dev_context *devc; + struct scope_config *model; + + if (!sdi || !(devc = sdi->priv)) + return SR_ERR_ARG; + + if ((pg_type = check_probe_group(devc, probe_group)) == PG_INVALID) + return SR_ERR; - (void)sdi; - (void)data; + ret = SR_ERR_NA; + model = devc->model_config; - ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_NUM_TIMEBASE: + *data = g_variant_new_int32(model->num_xdivs); + ret = SR_OK; + break; + + case SR_CONF_NUM_VDIV: + if (pg_type == PG_NONE) { + sr_err("No probe group specified."); + return SR_ERR_PROBE_GROUP; + + } else if (pg_type == PG_ANALOG) { + for (i = 0; i < model->analog_channels; ++i) { + if (probe_group == &devc->analog_groups[i]) { + *data = g_variant_new_int32(model->num_ydivs); + ret = SR_OK; + break; + } + } + + } else { + ret = SR_ERR_NA; + } + break; + default: - return SR_ERR_NA; + ret = SR_ERR_NA; } return ret; } -static int config_set(int key, GVariant *data, const struct sr_dev_inst *sdi) +static GVariant *build_tuples(const uint64_t (*array)[][2], unsigned int n) +{ + unsigned int i; + + GVariant *rational[2]; + GVariantBuilder gvb; + + g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY); + + for (i = 0; i < n; i++) { + rational[0] = g_variant_new_uint64((*array)[i][0]); + rational[1] = g_variant_new_uint64((*array)[i][1]); + + /* FIXME valgrind reports a memory leak here */ + g_variant_builder_add_value(&gvb, g_variant_new_tuple(rational, 2)); + } + + return g_variant_builder_end(&gvb); +} + +static int config_set(int key, GVariant *data, const struct sr_dev_inst *sdi, + const struct sr_probe_group *probe_group) { int ret; + int pg_type; + unsigned int i; + char command[MAX_COMMAND_SIZE]; - (void)data; + struct dev_context *devc; + struct scope_config *model; + struct scope_state *state; - if (sdi->status != SR_ST_ACTIVE) - return SR_ERR_DEV_CLOSED; + if (!sdi || !(devc = sdi->priv)) + return SR_ERR_ARG; + + if ((pg_type = check_probe_group(devc, probe_group)) == PG_INVALID) + return SR_ERR; + + model = devc->model_config; + state = devc->model_state; + + ret = SR_ERR_NA; - ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_LIMIT_FRAMES: + devc->frame_limit = g_variant_get_uint64(data); + ret = SR_OK; + break; + + case SR_CONF_TRIGGER_SOURCE: + { + const char *tmp; + + tmp = g_variant_get_string(data, NULL); + + for (i = 0; (*model->trigger_sources)[i]; i++) { + if (!g_strcmp0(tmp, (*model->trigger_sources)[i])) { + state->trigger_source = i; + + g_snprintf(command, sizeof(command), + (*model->scpi_dialect)[SCPI_CMD_SET_TRIGGER_SOURCE], + (*model->trigger_sources)[i]); + + ret = sr_scpi_send(sdi->conn, command); + break; + } + } + } + break; + + case SR_CONF_VDIV: + { + unsigned int j; + uint64_t p, q; + + if (pg_type == PG_NONE) { + sr_err("No probe group specified."); + return SR_ERR_PROBE_GROUP; + } + + g_variant_get(data, "(tt)", &p, &q); + + for (i = 0; i < model->num_vdivs; i++) { + if (p == (*model->vdivs)[i][0] && + q == (*model->vdivs)[i][1]){ + for (j = 1; j <= model->analog_channels; ++j) { + if (probe_group == &devc->analog_groups[j - 1]) { + state->analog_channels[j-1].vdiv = (float) p / q; + g_snprintf(command, sizeof(command), + (*model->scpi_dialect)[SCPI_CMD_SET_VERTICAL_DIV], + j, state->analog_channels[j-1].vdiv); + + if (sr_scpi_send(sdi->conn, command) != SR_OK || + sr_scpi_get_opc(sdi->conn) != SR_OK) + return SR_ERR; + + break; + } + } + + ret = SR_OK; + break; + } + } + } + break; + + case SR_CONF_TIMEBASE: + { + uint64_t p, q; + + g_variant_get(data, "(tt)", &p, &q); + + for (i = 0; i < model->num_timebases; i++) { + if (p == (*model->timebases)[i][0] && + q == (*model->timebases)[i][1]){ + state->timebase = (float) p / q; + g_snprintf(command, sizeof(command), + (*model->scpi_dialect)[SCPI_CMD_SET_TIMEBASE], + state->timebase); + + ret = sr_scpi_send(sdi->conn, command); + break; + } + } + } + break; + + case SR_CONF_HORIZ_TRIGGERPOS: + { + double tmp; + + tmp = g_variant_get_double(data); + + if (tmp < 0.0 || tmp > 1.0) + return SR_ERR; + + state->horiz_triggerpos = -(tmp - 0.5) * state->timebase * model->num_xdivs; + g_snprintf(command, sizeof(command), + (*model->scpi_dialect)[SCPI_CMD_SET_HORIZ_TRIGGERPOS], + state->horiz_triggerpos); + + ret = sr_scpi_send(sdi->conn, command); + } + break; + + case SR_CONF_TRIGGER_SLOPE: + { + uint64_t tmp; + + tmp = g_variant_get_uint64(data); + + if (tmp != 0 && tmp != 1) + return SR_ERR; + + state->trigger_slope = tmp; + + g_snprintf(command, sizeof(command), + (*model->scpi_dialect)[SCPI_CMD_SET_TRIGGER_SLOPE], + tmp ? "POS" : "NEG"); + + ret = sr_scpi_send(sdi->conn, command); + } + break; + + case SR_CONF_COUPLING: + { + unsigned int j; + const char *tmp; + + if (pg_type == PG_NONE) { + sr_err("No probe group specified."); + return SR_ERR_PROBE_GROUP; + } + + tmp = g_variant_get_string(data, NULL); + + for (i = 0; (*model->coupling_options)[i]; i++) { + if (!strcmp(tmp, (*model->coupling_options)[i])) { + for (j = 1; j <= model->analog_channels; ++j) { + if (probe_group == &devc->analog_groups[j - 1]) { + state->analog_channels[j-1].coupling = i; + + g_snprintf(command, sizeof(command), + (*model->scpi_dialect)[SCPI_CMD_SET_COUPLING], + j, tmp); + + if (sr_scpi_send(sdi->conn, command) != SR_OK || + sr_scpi_get_opc(sdi->conn) != SR_OK) + return SR_ERR; + break; + } + } + + ret = SR_OK; + break; + } + } + } + break; + default: ret = SR_ERR_NA; + break; } + if (ret == SR_OK) + ret = sr_scpi_get_opc(sdi->conn); + return ret; } -static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi) +static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi, + const struct sr_probe_group *probe_group) { - int ret; + int pg_type; + + struct dev_context *devc; + struct scope_config *model; + + if (!sdi || !(devc = sdi->priv)) + return SR_ERR_ARG; - (void)sdi; - (void)data; + if ((pg_type = check_probe_group(devc, probe_group)) == PG_INVALID) + return SR_ERR; + + model = devc->model_config; - ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_DEVICE_OPTIONS: + if (pg_type == PG_NONE) { + *data = g_variant_new_fixed_array(G_VARIANT_TYPE_INT32, + model->hw_caps, + model->num_hwcaps, + sizeof(int32_t)); + } else if (pg_type == PG_ANALOG) { + *data = g_variant_new_fixed_array(G_VARIANT_TYPE_INT32, + model->analog_hwcaps, + model->num_analog_hwcaps, + sizeof(int32_t)); + } else { + *data = g_variant_new_fixed_array(G_VARIANT_TYPE_INT32, + NULL, 0, sizeof(int32_t)); + } + break; + + case SR_CONF_COUPLING: + if (pg_type == PG_NONE) + return SR_ERR_PROBE_GROUP; + + *data = g_variant_new_strv(*model->coupling_options, + g_strv_length((char **) *model->coupling_options)); + break; + + case SR_CONF_TRIGGER_SOURCE: + *data = g_variant_new_strv(*model->trigger_sources, + g_strv_length((char **) *model->trigger_sources)); + break; + + case SR_CONF_TIMEBASE: + *data = build_tuples(model->timebases, model->num_timebases); + break; + + case SR_CONF_VDIV: + if (pg_type == PG_NONE) + return SR_ERR_PROBE_GROUP; + + *data = build_tuples(model->vdivs, model->num_vdivs); + break; + default: return SR_ERR_NA; } - return ret; + return SR_OK; } -static int dev_acquisition_start(const struct sr_dev_inst *sdi, - void *cb_data) +SR_PRIV int hmo_request_data(const struct sr_dev_inst *sdi) { - (void)sdi; - (void)cb_data; + char command[MAX_COMMAND_SIZE]; + + struct sr_probe *probe; + struct dev_context *devc; + struct scope_config *model; + + devc = sdi->priv; + model = devc->model_config; + + probe = devc->current_probe->data; + + switch (probe->type) { + case SR_PROBE_ANALOG: + g_snprintf(command, sizeof(command), + (*model->scpi_dialect)[SCPI_CMD_GET_ANALOG_DATA], + probe->index + 1); + break; + case SR_PROBE_LOGIC: + g_snprintf(command, sizeof(command), + (*model->scpi_dialect)[SCPI_CMD_GET_DIG_DATA], + probe->index < 8 ? 1 : 2); + break; + default: + sr_err("Invalid probe type"); + break; + } + + return sr_scpi_send(sdi->conn, command); +} + +static int hmo_check_probes(GSList *probes) +{ + GSList *l; + gboolean enabled_pod1; + gboolean enabled_pod2; + gboolean enabled_chan3; + gboolean enabled_chan4; + + struct sr_probe *probe; + + enabled_pod1 = FALSE; + enabled_pod2 = FALSE; + enabled_chan3 = FALSE; + enabled_chan4 = FALSE; + + for (l = probes; l; l = l->next) { + probe = l->data; + + switch (probe->type) { + case SR_PROBE_ANALOG: + if (probe->index == 2) + enabled_chan3 = TRUE; + else if (probe->index == 3) + enabled_chan4 = TRUE; + break; + + case SR_PROBE_LOGIC: + if (probe->index < 8) + enabled_pod1 = TRUE; + else + enabled_pod2 = TRUE; + break; + + default: + return SR_ERR; + } + } + + if ((enabled_pod1 && enabled_chan3) || + (enabled_pod2 && enabled_chan4)) + return SR_ERR; + + return SR_OK; +} + +static int hmo_setup_probes(const struct sr_dev_inst *sdi) +{ + GSList *l; + unsigned int i; + gboolean *pod_enabled; + char command[MAX_COMMAND_SIZE]; + + struct scope_state *state; + struct scope_config *model; + + struct sr_probe *probe; + struct dev_context *devc; + struct sr_serial_dev_inst *serial; + + devc = sdi->priv; + serial = sdi->conn; + state = devc->model_state; + model = devc->model_config; + + pod_enabled = g_try_malloc0(sizeof(gboolean) * model->digital_pods); + + for (l = sdi->probes; l; l = l->next) { + probe = l->data; + + switch (probe->type) { + case SR_PROBE_ANALOG: + { + if (probe->enabled != state->analog_channels[probe->index].state) { + g_snprintf(command, sizeof(command), + (*model->scpi_dialect)[SCPI_CMD_SET_ANALOG_CHAN_STATE], + probe->index + 1, probe->enabled); + + if (sr_scpi_send(serial, command) != SR_OK) + return SR_ERR; + state->analog_channels[probe->index].state = probe->enabled; + } + } + break; + + case SR_PROBE_LOGIC: + { + /* + * A digital POD needs to be enabled for every group of + * 8 probes. + */ + if (probe->enabled) + pod_enabled[probe->index < 8 ? 0 : 1] = TRUE; + + if (probe->enabled != state->digital_channels[probe->index]) { + g_snprintf(command, sizeof(command), + (*model->scpi_dialect)[SCPI_CMD_SET_DIG_CHAN_STATE], + probe->index, probe->enabled); + + if (sr_scpi_send(serial, command) != SR_OK) + return SR_ERR; + + state->digital_channels[probe->index] = probe->enabled; + } + } + break; + + default: + return SR_ERR; + } + } + + for (i = 1; i <= model->digital_pods; ++i) { + if (state->digital_pods[i-1] != pod_enabled[i-1]) { + g_snprintf(command, sizeof(command), + (*model->scpi_dialect)[SCPI_CMD_SET_DIG_POD_STATE], + i, pod_enabled[i-1]); + + if (sr_scpi_send(serial, command) != SR_OK) + return SR_ERR; + + state->digital_pods[i-1] = pod_enabled[i-1]; + } + } + + g_free(pod_enabled); + + return SR_OK; +} + +static int dev_acquisition_start(const struct sr_dev_inst *sdi, void *cb_data) +{ + GSList *l; + gboolean digital_added; + + struct sr_probe *probe; + struct dev_context *devc; + struct sr_serial_dev_inst *serial; if (sdi->status != SR_ST_ACTIVE) return SR_ERR_DEV_CLOSED; - /* TODO: configure hardware, reset acquisition state, set up - * callbacks and send header packet. */ + serial = sdi->conn; + devc = sdi->priv; + digital_added = FALSE; + + for (l = sdi->probes; l; l = l->next) { + probe = l->data; + + if (probe->enabled) { + /* Only add a single digital probe */ + if (probe->type != SR_PROBE_LOGIC || !digital_added) { + devc->enabled_probes = g_slist_append(devc->enabled_probes, + probe); + if (probe->type == SR_PROBE_LOGIC) + digital_added = TRUE; + } + } + } - return SR_OK; + if (!devc->enabled_probes) + return SR_ERR; + + if (hmo_check_probes(devc->enabled_probes) != SR_OK) { + sr_err("Invalid probe configuration specified!"); + return SR_ERR_NA; + } + + if (hmo_setup_probes(sdi) != SR_OK) { + sr_err("Failed to setup probe configuration!"); + return SR_ERR; + } + + sr_source_add(serial->fd, G_IO_IN, 50, hameg_hmo_receive_data, (void *)sdi); + + /* Send header packet to the session bus. */ + std_session_send_df_header(cb_data, LOG_PREFIX); + + devc->current_probe = devc->enabled_probes; + + return hmo_request_data(sdi); } static int dev_acquisition_stop(struct sr_dev_inst *sdi, void *cb_data) { + struct dev_context *devc; + struct sr_serial_dev_inst *serial; + (void)cb_data; if (sdi->status != SR_ST_ACTIVE) return SR_ERR_DEV_CLOSED; - /* TODO: stop acquisition. */ + devc = sdi->priv; + + g_slist_free(devc->enabled_probes); + devc->enabled_probes = NULL; + serial = sdi->conn; + sr_source_remove(serial->fd); return SR_OK; } diff --git a/hardware/hameg-hmo/protocol.c b/hardware/hameg-hmo/protocol.c index 64b35e9..cd94f0c 100644 --- a/hardware/hameg-hmo/protocol.c +++ b/hardware/hameg-hmo/protocol.c @@ -19,10 +19,614 @@ #include "protocol.h" +static const char *manufacturers[] = { + "HAMEG", +}; + +static const char *hameg_scpi_dialect[] = { + [SCPI_CMD_GET_DIG_DATA] = ":POD%d:DATA?", + [SCPI_CMD_GET_TIMEBASE] = ":TIM:SCAL?", + [SCPI_CMD_SET_TIMEBASE] = ":TIM:SCAL %E", + [SCPI_CMD_GET_COUPLING] = ":CHAN%d:COUP?", + [SCPI_CMD_SET_COUPLING] = ":CHAN%d:COUP %s", + [SCPI_CMD_GET_ANALOG_DATA] = ":CHAN%d:DATA?", + [SCPI_CMD_GET_VERTICAL_DIV] = ":CHAN%d:SCAL?", + [SCPI_CMD_SET_VERTICAL_DIV] = ":CHAN%d:SCAL %E", + [SCPI_CMD_GET_DIG_POD_STATE] = ":POD%d:STAT?", + [SCPI_CMD_SET_DIG_POD_STATE] = ":POD%d:STAT %d", + [SCPI_CMD_GET_TRIGGER_SLOPE] = ":TRIG:A:EDGE:SLOP?", + [SCPI_CMD_SET_TRIGGER_SLOPE] = ":TRIG:A:EDGE:SLOP %s", + [SCPI_CMD_GET_TRIGGER_SOURCE] = ":TRIG:A:SOUR?", + [SCPI_CMD_SET_TRIGGER_SOURCE] = ":TRIG:A:SOUR %s", + [SCPI_CMD_GET_DIG_CHAN_STATE] = ":LOG%d:STAT?", + [SCPI_CMD_SET_DIG_CHAN_STATE] = ":LOG%d:STAT %d", + [SCPI_CMD_GET_VERTICAL_OFFSET] = ":CHAN%d:POS?", + [SCPI_CMD_GET_HORIZ_TRIGGERPOS] = ":TIM:POS?", + [SCPI_CMD_SET_HORIZ_TRIGGERPOS] = ":TIM:POS %E", + [SCPI_CMD_GET_ANALOG_CHAN_STATE] = ":CHAN%d:STAT?", + [SCPI_CMD_SET_ANALOG_CHAN_STATE] = ":CHAN%d:STAT %d", +}; + +static const int32_t hmo_hwcaps[] = { + SR_CONF_OSCILLOSCOPE, + SR_CONF_TRIGGER_SOURCE, + SR_CONF_TIMEBASE, + SR_CONF_NUM_TIMEBASE, + SR_CONF_TRIGGER_SLOPE, + SR_CONF_HORIZ_TRIGGERPOS, +}; + +static const int32_t hmo_analog_caps[] = { + SR_CONF_NUM_VDIV, + SR_CONF_COUPLING, + SR_CONF_VDIV, +}; + +static const char *hmo_coupling_options[] = { + "AC", + "ACL", + "DC", + "GND", + NULL, +}; + +static const char *scope_trigger_slopes[] = { + "POS", + "NEG", + NULL, +}; + +static const char *hmo_compact2_trigger_sources[] = { + "CH1", + "CH2", + "LINE", + "EXT", + "D0", + "D1", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + NULL, +}; + +static const char *hmo_compact4_trigger_sources[] = { + "CH1", + "CH2", + "CH3", + "CH4", + "LINE", + "EXT", + "D0", + "D1", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + NULL, +}; + +static const uint64_t hmo_timebases[][2] = { + /* nanoseconds */ + { 2, 1000000000 }, + { 5, 1000000000 }, + { 10, 1000000000 }, + { 20, 1000000000 }, + { 50, 1000000000 }, + { 100, 1000000000 }, + { 200, 1000000000 }, + { 500, 1000000000 }, + /* microseconds */ + { 1, 1000000 }, + { 2, 1000000 }, + { 5, 1000000 }, + { 10, 1000000 }, + { 20, 1000000 }, + { 50, 1000000 }, + { 100, 1000000 }, + { 200, 1000000 }, + { 500, 1000000 }, + /* milliseconds */ + { 1, 1000 }, + { 2, 1000 }, + { 5, 1000 }, + { 10, 1000 }, + { 20, 1000 }, + { 50, 1000 }, + { 100, 1000 }, + { 200, 1000 }, + { 500, 1000 }, + /* seconds */ + { 1, 1 }, + { 2, 1 }, + { 5, 1 }, + { 10, 1 }, + { 20, 1 }, + { 50, 1 }, +}; + +static const uint64_t hmo_vdivs[][2] = { + /* millivolts */ + { 1, 1000 }, + { 2, 1000 }, + { 5, 1000 }, + { 10, 1000 }, + { 20, 1000 }, + { 50, 1000 }, + { 100, 1000 }, + { 200, 1000 }, + { 500, 1000 }, + /* volts */ + { 1, 1 }, + { 2, 1 }, + { 5, 1 }, + { 10, 1 }, +}; + +static const char *scope_analog_probe_names[] = { + "CH1", + "CH2", + "CH3", + "CH4", +}; + +static const char *scope_digital_probe_names[] = { + "D0", + "D1", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "D10", + "D11", + "D12", + "D13", + "D14", + "D15", +}; + +static struct scope_config scope_models[] = { + { + .name = {"HMO722", "HMO1022", "HMO1522", "HMO2022", NULL}, + .analog_channels = 2, + .digital_channels = 8, + .digital_pods = 1, + + .analog_names = &scope_analog_probe_names, + .digital_names = &scope_digital_probe_names, + + .hw_caps = &hmo_hwcaps, + .num_hwcaps = ARRAY_SIZE(hmo_hwcaps), + + .analog_hwcaps = &hmo_analog_caps, + .num_analog_hwcaps = ARRAY_SIZE(hmo_analog_caps), + + .coupling_options = &hmo_coupling_options, + .trigger_sources = &hmo_compact2_trigger_sources, + .trigger_slopes = &scope_trigger_slopes, + + .timebases = &hmo_timebases, + .num_timebases = ARRAY_SIZE(hmo_timebases), + + .vdivs = &hmo_vdivs, + .num_vdivs = ARRAY_SIZE(hmo_vdivs), + + .num_xdivs = 12, + .num_ydivs = 8, + + .scpi_dialect = &hameg_scpi_dialect, + }, + { + .name = {"HMO724", "HMO1024", "HMO1524", "HMO2024", NULL}, + .analog_channels = 4, + .digital_channels = 8, + .digital_pods = 1, + + .analog_names = &scope_analog_probe_names, + .digital_names = &scope_digital_probe_names, + + .hw_caps = &hmo_hwcaps, + .num_hwcaps = ARRAY_SIZE(hmo_hwcaps), + + .analog_hwcaps = &hmo_analog_caps, + .num_analog_hwcaps = ARRAY_SIZE(hmo_analog_caps), + + .coupling_options = &hmo_coupling_options, + .trigger_sources = &hmo_compact4_trigger_sources, + .trigger_slopes = &scope_trigger_slopes, + + .timebases = &hmo_timebases, + .num_timebases = ARRAY_SIZE(hmo_timebases), + + .vdivs = &hmo_vdivs, + .num_vdivs = ARRAY_SIZE(hmo_vdivs), + + .num_xdivs = 12, + .num_ydivs = 8, + + .scpi_dialect = &hameg_scpi_dialect, + }, +}; + +static int check_manufacturer(const char *manufacturer) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(manufacturers); ++i) + if (strcmp(manufacturer, manufacturers[i]) == 0) + return SR_OK; + + return SR_ERR; +} + +static void scope_state_dump(struct scope_config *config, + struct scope_state *state) +{ + unsigned int i; + + for (i = 0; i < config->analog_channels; ++i) { + sr_info("State of analog channel %d -> %s : %s %.3eV %.3e offset", i+1, + state->analog_channels[i].state ? "On" : "Off", + (*config->coupling_options)[state->analog_channels[i].coupling], + state->analog_channels[i].vdiv, state->analog_channels[i].vertical_offset); + } + + for (i = 0; i < config->digital_channels; ++i) { + sr_info("State of digital channel %d -> %s", i, + state->digital_channels[i] ? "On" : "Off"); + } + + for (i = 0; i < config->digital_pods; ++i) { + sr_info("State of digital POD %d -> %s", i, + state->digital_pods[i] ? "On" : "Off"); + } + + sr_info("Current timebase: %.2es", state->timebase); + sr_info("Current trigger: %s (source), %s (slope) %.2e (offset)", + (*config->trigger_sources)[state->trigger_source], + (*config->trigger_slopes)[state->trigger_slope], + state->horiz_triggerpos); +} + +static int scope_state_get_array_option(struct sr_serial_dev_inst *serial, + const char *command, const char *(*array)[], + int *result) +{ + char *tmp; + unsigned int i; + + if (sr_scpi_get_string(serial, command, &tmp) != SR_OK) { + if (tmp) + g_free(tmp); + return SR_ERR; + } + + for (i = 0; (*array)[i]; ++i) { + if (!g_strcmp0(tmp, (*array)[i])) { + *result = i; + g_free(tmp); + tmp = NULL; + break; + } + } + + if (tmp) { + g_free(tmp); + return SR_ERR; + } + + return SR_OK; +} + +static int analog_channel_state_get(struct sr_serial_dev_inst *serial, + struct scope_config *config, + struct scope_state *state) +{ + unsigned int i; + char command[MAX_COMMAND_SIZE]; + + for (i = 0; i < config->analog_channels; ++i) { + g_snprintf(command, sizeof(command), + (*config->scpi_dialect)[SCPI_CMD_GET_ANALOG_CHAN_STATE], + i + 1); + + if (sr_scpi_get_bool(serial, command, + &state->analog_channels[i].state) != SR_OK) + return SR_ERR; + + g_snprintf(command, sizeof(command), + (*config->scpi_dialect)[SCPI_CMD_GET_VERTICAL_DIV], + i + 1); + + if (sr_scpi_get_float(serial, command, + &state->analog_channels[i].vdiv) != SR_OK) + return SR_ERR; + + g_snprintf(command, sizeof(command), + (*config->scpi_dialect)[SCPI_CMD_GET_VERTICAL_OFFSET], + i + 1); + + if (sr_scpi_get_float(serial, command, + &state->analog_channels[i].vertical_offset) != SR_OK) + return SR_ERR; + + g_snprintf(command, sizeof(command), + (*config->scpi_dialect)[SCPI_CMD_GET_COUPLING], + i + 1); + + if (scope_state_get_array_option(serial, command, config->coupling_options, + &state->analog_channels[i].coupling) != SR_OK) + return SR_ERR; + } + + return SR_OK; +} + +static int digital_channel_state_get(struct sr_serial_dev_inst *serial, + struct scope_config *config, + struct scope_state *state) +{ + unsigned int i; + char command[MAX_COMMAND_SIZE]; + + for (i = 0; i < config->digital_channels; ++i) { + g_snprintf(command, sizeof(command), + (*config->scpi_dialect)[SCPI_CMD_GET_DIG_CHAN_STATE], + i); + + if (sr_scpi_get_bool(serial, command, + &state->digital_channels[i]) != SR_OK) + return SR_ERR; + } + + for (i = 0; i < config->digital_pods; ++i) { + g_snprintf(command, sizeof(command), + (*config->scpi_dialect)[SCPI_CMD_GET_DIG_POD_STATE], + i + 1); + + if (sr_scpi_get_bool(serial, command, + &state->digital_pods[i]) != SR_OK) + return SR_ERR; + } + + return SR_OK; +} + +SR_PRIV int scope_state_get(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct scope_state *state; + struct scope_config *config; + + devc = sdi->priv; + config = devc->model_config; + state = devc->model_state; + + if (analog_channel_state_get(sdi->conn, config, state) != SR_OK) + return SR_ERR; + + if (digital_channel_state_get(sdi->conn, config, state) != SR_OK) + return SR_ERR; + + /* TODO check if value is sensible */ + if (sr_scpi_get_float(sdi->conn, (*config->scpi_dialect)[SCPI_CMD_GET_TIMEBASE], + &state->timebase) != SR_OK) + return SR_ERR; + + if (sr_scpi_get_float(sdi->conn, (*config->scpi_dialect)[SCPI_CMD_GET_HORIZ_TRIGGERPOS], + &state->horiz_triggerpos) != SR_OK) + return SR_ERR; + + if (scope_state_get_array_option(sdi->conn, (*config->scpi_dialect)[SCPI_CMD_GET_TRIGGER_SOURCE], + config->trigger_sources, &state->trigger_source) != SR_OK) + return SR_ERR; + + if (scope_state_get_array_option(sdi->conn, (*config->scpi_dialect)[SCPI_CMD_GET_TRIGGER_SLOPE], + config->trigger_slopes, &state->trigger_slope) != SR_OK) + return SR_ERR; + + scope_state_dump(config, state); + + return SR_OK; +} + +SR_PRIV struct scope_state *scope_state_new(struct scope_config *config) +{ + struct scope_state *state; + + if (!(state = g_try_malloc0(sizeof(struct scope_state)))) + return NULL; + + if (!(state->analog_channels = g_try_malloc0_n(config->analog_channels, + sizeof(struct analog_channel_state)))) + goto fail; + + if (!(state->digital_channels = g_try_malloc0_n(config->digital_channels, + sizeof(gboolean)))) + goto fail; + + if (!(state->digital_pods = g_try_malloc0_n(config->digital_pods, + sizeof(gboolean)))) + goto fail; + + return state; + +fail: + if (state->analog_channels) + g_free(state->analog_channels); + if (state->digital_channels) + g_free(state->digital_channels); + if (state->digital_pods) + g_free(state->digital_pods); + g_free(state); + + return NULL; +} + +SR_PRIV void scope_state_free(struct scope_state *state) +{ + g_free(state->analog_channels); + g_free(state->digital_channels); + g_free(state->digital_pods); + g_free(state); +} + +SR_PRIV int hmo_init_device(struct sr_dev_inst *sdi) +{ + char tmp[25]; + int model_index; + unsigned int i; + unsigned int j; + + struct sr_probe *probe; + struct dev_context *devc; + + devc = sdi->priv; + model_index = -1; + + /* Find the exact model */ + for (i = 0; i < ARRAY_SIZE(scope_models); i++) { + for (j = 0; scope_models[i].name[j]; j++) { + if (!strcmp(sdi->model, scope_models[i].name[j])) { + model_index = i; + break; + } + } + if (model_index != -1) + break; + } + + if (model_index == -1) { + sr_dbg("Unsupported HMO device"); + return SR_ERR_NA; + } + + if (!(devc->analog_groups = g_try_malloc0(sizeof(struct sr_probe_group) * + scope_models[model_index].analog_channels))) + return SR_ERR_MALLOC; + + if (!(devc->digital_groups = g_try_malloc0(sizeof(struct sr_probe_group) * + scope_models[model_index].digital_pods))) + return SR_ERR_MALLOC; + + /* Add analog channels */ + for (i = 0; i < scope_models[model_index].analog_channels; i++) { + if (!(probe = sr_probe_new(i, SR_PROBE_ANALOG, TRUE, + (*scope_models[model_index].analog_names)[i]))) + return SR_ERR_MALLOC; + sdi->probes = g_slist_append(sdi->probes, probe); + + devc->analog_groups[i].name = (char *) (*scope_models[model_index].analog_names)[i]; + devc->analog_groups[i].probes = g_slist_append(NULL, probe); + + sdi->probe_groups = g_slist_append(sdi->probe_groups, + &devc->analog_groups[i]); + } + + /* Add digital probe groups */ + for (i = 0; i < scope_models[model_index].digital_pods; ++i) { + g_snprintf(tmp, 25, "POD%d", i); + devc->digital_groups[i].name = g_strdup(tmp); + sdi->probe_groups = g_slist_append(sdi->probe_groups, + &devc->digital_groups[i < 8 ? 0 : 1]); + } + + /* Add digital channels */ + for (i = 0; i < scope_models[model_index].digital_channels; i++) { + if (!(probe = sr_probe_new(i, SR_PROBE_LOGIC, TRUE, + (*scope_models[model_index].digital_names)[i]))) + return SR_ERR_MALLOC; + sdi->probes = g_slist_append(sdi->probes, probe); + + devc->digital_groups[i < 8 ? 0 : 1].probes = g_slist_append(devc->digital_groups[i < 8 ? 0 : 1].probes, + probe); + } + + devc->model_config = &scope_models[model_index]; + devc->frame_limit = 0; + + if (!(devc->model_state = scope_state_new(devc->model_config))) + return SR_ERR_MALLOC; + + return SR_OK; +} + +SR_PRIV struct sr_dev_inst *hameg_probe_serial_device(const char *serial_device, + const char *serial_options) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct sr_scpi_hw_info *hw_info; + struct sr_serial_dev_inst *serial; + + sdi = NULL; + devc = NULL; + serial = NULL; + hw_info = NULL; + + if (!(serial = sr_serial_dev_inst_new(serial_device, serial_options))) + goto fail; + + sr_info("Probing %s.", serial_device); + if (serial_open(serial, SERIAL_RDWR | SERIAL_NONBLOCK) != SR_OK) + goto fail; + + if (sr_scpi_get_hw_id(serial, &hw_info) != SR_OK) { + sr_info("Couldn't get IDN response"); + goto fail; + } + + if (check_manufacturer(hw_info->manufacturer) != SR_OK) + goto fail; + + if (!(sdi = sr_dev_inst_new(0, SR_ST_ACTIVE, + hw_info->manufacturer, hw_info->model, + hw_info->firmware_version))) { + goto fail; + } + sr_scpi_hw_info_free(hw_info); + hw_info = NULL; + + if (!(devc = g_try_malloc0(sizeof(struct dev_context)))) + goto fail; + + sdi->driver = di; + sdi->priv = devc; + sdi->inst_type = SR_INST_SERIAL; + sdi->conn = serial; + + if (hmo_init_device(sdi) != SR_OK) + goto fail; + + return sdi; + +fail: + if (hw_info) + sr_scpi_hw_info_free(hw_info); + if (serial) + sr_serial_dev_inst_free(serial); + if (sdi) + sr_dev_inst_free(sdi); + if (devc) + g_free(devc); + + return NULL; +} + SR_PRIV int hameg_hmo_receive_data(int fd, int revents, void *cb_data) { - const struct sr_dev_inst *sdi; + struct sr_probe *probe; + struct sr_dev_inst *sdi; struct dev_context *devc; + struct sr_datafeed_packet packet; (void)fd; @@ -33,7 +637,81 @@ SR_PRIV int hameg_hmo_receive_data(int fd, int revents, void *cb_data) return TRUE; if (revents == G_IO_IN) { - /* TODO */ + probe = devc->current_probe->data; + + switch (probe->type) { + case SR_PROBE_ANALOG: + { + GArray *data; + struct sr_datafeed_analog analog; + + if (sr_scpi_get_floatv(sdi->conn, NULL, &data) != SR_OK) { + if (data) + g_array_free(data, TRUE); + + return TRUE; + } + + packet.type = SR_DF_FRAME_BEGIN; + sr_session_send(sdi, &packet); + + analog.probes = g_slist_append(NULL, probe); + analog.num_samples = data->len; + analog.data = (float *) data->data; + analog.mq = SR_MQ_VOLTAGE; + analog.unit = SR_UNIT_VOLT; + analog.mqflags = 0; + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + sr_session_send(cb_data, &packet); + g_slist_free(analog.probes); + g_array_free(data, TRUE); + } + break; + + case SR_PROBE_LOGIC: + { + GArray *data; + struct sr_datafeed_logic logic; + + if (sr_scpi_get_uint8v(sdi->conn, NULL, &data) != SR_OK) { + if (data) + g_free(data); + return TRUE; + } + + packet.type = SR_DF_FRAME_BEGIN; + sr_session_send(sdi, &packet); + + logic.length = data->len; + logic.unitsize = 1; + logic.data = data->data; + packet.type = SR_DF_LOGIC; + packet.payload = &logic; + sr_session_send(cb_data, &packet); + g_array_free(data, TRUE); + } + break; + + default: + sr_err("Invalid probe type"); + break; + } + + packet.type = SR_DF_FRAME_END; + sr_session_send(sdi, &packet); + + if (devc->current_probe->next) { + devc->current_probe = devc->current_probe->next; + hmo_request_data(sdi); + } else if (++devc->num_frames == devc->frame_limit) { + packet.type = SR_DF_END; + sr_session_send(sdi, &packet); + sdi->driver->dev_acquisition_stop(sdi, cb_data); + } else { + devc->current_probe = devc->enabled_probes; + hmo_request_data(sdi); + } } return TRUE; diff --git a/hardware/hameg-hmo/protocol.h b/hardware/hameg-hmo/protocol.h index 0c06a93..9f27f01 100644 --- a/hardware/hameg-hmo/protocol.h +++ b/hardware/hameg-hmo/protocol.h @@ -20,8 +20,9 @@ #ifndef LIBSIGROK_HARDWARE_HAMEG_HMO_PROTOCOL_H #define LIBSIGROK_HARDWARE_HAMEG_HMO_PROTOCOL_H -#include <stdint.h> #include <glib.h> +#include <stdint.h> +#include <string.h> #include "libsigrok.h" #include "libsigrok-internal.h" @@ -34,18 +35,98 @@ #define sr_warn(s, args...) sr_warn(LOG_PREFIX s, ## args) #define sr_err(s, args...) sr_err(LOG_PREFIX s, ## args) +#define MAX_INSTRUMENT_VERSIONS 10 +#define MAX_COMMAND_SIZE 31 + +SR_PRIV struct sr_dev_driver hameg_hmo_driver_info; +static struct sr_dev_driver *di = &hameg_hmo_driver_info; + +enum { + PG_INVALID = -1, + PG_NONE, + PG_ANALOG, + PG_DIGITAL, +}; + +struct scope_config { + const char *name[MAX_INSTRUMENT_VERSIONS]; + const uint8_t analog_channels; + const uint8_t digital_channels; + const uint8_t digital_pods; + + const char *(*analog_names)[]; + const char *(*digital_names)[]; + + const int32_t (*hw_caps)[]; + const uint8_t num_hwcaps; + + const int32_t (*analog_hwcaps)[]; + const uint8_t num_analog_hwcaps; + + const char *(*coupling_options)[]; + const uint8_t num_coupling_options; + + const char *(*trigger_sources)[]; + const uint8_t num_trigger_sources; + + const char *(*trigger_slopes)[]; + + const uint64_t (*timebases)[][2]; + const uint8_t num_timebases; + + const uint64_t (*vdivs)[][2]; + const uint8_t num_vdivs; + + const uint8_t num_xdivs; + const uint8_t num_ydivs; + + const char *(*scpi_dialect)[]; +}; + +struct analog_channel_state { + int coupling; + + float vdiv; + float vertical_offset; + + gboolean state; +}; + +struct scope_state { + struct analog_channel_state *analog_channels; + gboolean *digital_channels; + gboolean *digital_pods; + + float timebase; + float horiz_triggerpos; + + int trigger_source; + int trigger_slope; +}; + /** Private, per-device-instance driver context. */ struct dev_context { - /* Model-specific information */ - - /* Acquisition settings */ + void *model_config; + void *model_state; - /* Operational state */ + struct sr_probe_group *analog_groups; + struct sr_probe_group *digital_groups; - /* Temporary state across callbacks */ + GSList *enabled_probes; + GSList *current_probe; + uint64_t num_frames; + uint64_t frame_limit; }; +SR_PRIV int hmo_init_device(struct sr_dev_inst *sdi); +SR_PRIV int hmo_request_data(const struct sr_dev_inst *sdi); SR_PRIV int hameg_hmo_receive_data(int fd, int revents, void *cb_data); +SR_PRIV struct sr_dev_inst *hameg_probe_serial_device(const char *serial_device, + const char *serial_options); + +SR_PRIV struct scope_state *scope_state_new(struct scope_config *config); +SR_PRIV void scope_state_free(struct scope_state *state); +SR_PRIV int scope_state_get(struct sr_dev_inst *sdi); #endif -- 1.8.4.2 |
From: Uwe H. <uw...@he...> - 2013-12-03 16:30:50
|
Hi, On Wed, Nov 20, 2013 at 10:21:13PM +0100, poljar (Damir Jelić) wrote: > This patch set consists of two parts, adding common functions for SCPI based > communications and adding support for Hameg HMO scopes. Merged with a few minor changes. Looks great, thanks a lot! Uwe. -- http://hermann-uwe.de | http://randomprojects.org | http://sigrok.org |
From: poljar (D. J. <po...@po...> - 2013-12-03 16:56:03
|
On Tue, Dec 03, 2013 at 05:30:40PM +0100, Uwe Hermann wrote: > Hi, > > On Wed, Nov 20, 2013 at 10:21:13PM +0100, poljar (Damir Jelić) wrote: > > This patch set consists of two parts, adding common functions for SCPI based > > communications and adding support for Hameg HMO scopes. > > Merged with a few minor changes. Looks great, thanks a lot! > Seems to work. Please comment in the future instead of fixing this yourself. Thanks, Damir. |