From: Mathias G. <mg...@za...> - 2013-10-20 19:42:54
|
Hi everybody, I cobbled together a first version of hardware support for the Rigol DS2xx2 series oscilloscopes (models DS2072, DS2102, DS2202). It is heavily based on the rigol-ds1xx2 code. Because of this it shares its quirks. Grabbing the displayed samples (1400 per channel) works when the scope is running, when stopped this only works if the horizontal settings haven't been changed. Enjoy, MGri diff -urN libsigrok.org/configure.ac libsigrok/configure.ac --- libsigrok.org/configure.ac 2013-10-18 22:18:36.000000000 +0200 +++ libsigrok/configure.ac 2013-10-20 19:09:30.884799042 +0200 @@ -184,6 +184,11 @@ [HW_RIGOL_DS1XX2="$enableval"], [HW_RIGOL_DS1XX2=$HW_ENABLED_DEFAULT]) +AC_ARG_ENABLE(rigol-ds2xx2, AC_HELP_STRING([--enable-rigol-ds2xx2], + [enable Rigol DS2xx2 support [default=yes]]), + [HW_RIGOL_DS2XX2="$enableval"], + [HW_RIGOL_DS2XX2=$HW_ENABLED_DEFAULT]) + AC_ARG_ENABLE(saleae-logic16, AC_HELP_STRING([--enable-saleae-logic16], [enable Saleae Logic16 support [default=yes]]), [HW_SALEAE_LOGIC16="$enableval"], @@ -417,6 +422,11 @@ AC_DEFINE(HAVE_HW_RIGOL_DS1XX2, 1, [Rigol DS1xx2 support]) fi +AM_CONDITIONAL(HW_RIGOL_DS2XX2, test x$HW_RIGOL_DS2XX2 = xyes) +if test "x$HW_RIGOL_DS2XX2" = "xyes"; then + AC_DEFINE(HAVE_HW_RIGOL_DS2XX2, 1, [Rigol DS2xx2 support]) +fi + AM_CONDITIONAL(HW_SERIAL_DMM, test x$HW_SERIAL_DMM = xyes) if test "x$HW_SERIAL_DMM" = "xyes"; then AC_DEFINE(HAVE_HW_SERIAL_DMM, 1, [Serial DMM support]) @@ -502,6 +512,7 @@ hardware/lascar-el-usb/Makefile hardware/mic-985xx/Makefile hardware/rigol-ds1xx2/Makefile + hardware/rigol-ds2xx2/Makefile hardware/saleae-logic16/Makefile hardware/tondaj-sl-814/Makefile hardware/victor-dmm/Makefile @@ -570,6 +581,7 @@ echo " - mic-985xx....................... $HW_MIC_985XX" echo " - openbench-logic-sniffer......... $HW_OLS" echo " - rigol-ds1xx2.................... $HW_RIGOL_DS1XX2" +echo " - rigol-ds2xx2.................... $HW_RIGOL_DS2XX2" echo " - saleae-logic16.................. $HW_SALEAE_LOGIC16" echo " - serial-dmm...................... $HW_SERIAL_DMM" echo " - tondaj-sl-814................... $HW_TONDAJ_SL_814" diff -urN libsigrok.org/hardware/Makefile.am libsigrok/hardware/Makefile.am --- libsigrok.org/hardware/Makefile.am 2013-09-14 01:11:11.000000000 +0200 +++ libsigrok/hardware/Makefile.am 2013-10-19 01:14:35.089165697 +0200 @@ -40,6 +40,7 @@ mic-985xx \ openbench-logic-sniffer \ rigol-ds1xx2 \ + rigol-ds2xx2 \ saleae-logic16 \ serial-dmm \ tondaj-sl-814 \ @@ -135,6 +136,10 @@ libsigrokhardware_la_LIBADD += rigol-ds1xx2/libsigrok_hw_rigol_ds1xx2.la endif +if HW_RIGOL_DS2XX2 +libsigrokhardware_la_LIBADD += rigol-ds2xx2/libsigrok_hw_rigol_ds2xx2.la +endif + if HW_SALEAE_LOGIC16 libsigrokhardware_la_LIBADD += saleae-logic16/libsigrok_hw_saleae_logic16.la endif diff -urN libsigrok.org/hardware/rigol-ds2xx2/api.c libsigrok/hardware/rigol-ds2xx2/api.c --- libsigrok.org/hardware/rigol-ds2xx2/api.c 1970-01-01 01:00:00.000000000 +0100 +++ libsigrok/hardware/rigol-ds2xx2/api.c 2013-10-20 16:30:44.966487179 +0200 @@ -0,0 +1,626 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2012 Martin Ling <mar...@ea...> + * Copyright (C) 2013 Bert Vermeulen <be...@bi...> + * Copyright (C) 2013 Mathias Grimmberger <mg...@za...> + * + * 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 <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <glib.h> +#include "libsigrok.h" +#include "libsigrok-internal.h" +#include "protocol.h" + +/* Based on api.c from Rigol DS1xx2 driver. */ + +static const int32_t hwopts[] = { + SR_CONF_CONN, +}; + +static const int32_t hwcaps[] = { + SR_CONF_OSCILLOSCOPE, + SR_CONF_TIMEBASE, + SR_CONF_TRIGGER_SOURCE, + SR_CONF_TRIGGER_SLOPE, + SR_CONF_HORIZ_TRIGGERPOS, + SR_CONF_VDIV, + SR_CONF_COUPLING, + SR_CONF_NUM_TIMEBASE, + SR_CONF_NUM_VDIV, +}; + +static const uint64_t timebases[][2] = { + /* nanoseconds */ + { 2, 1000000000 }, + { 5, 1000000000 }, + { 10, 1000000000 }, + { 20, 1000000000 }, + { 50, 1000000000 }, + { 100, 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 }, + { 100, 1 }, + { 200, 1 }, + { 500, 1 }, +/* { 1000, 1 }, Confuses other code? */ +}; + +static const uint64_t vdivs[][2] = { + /* microvolts */ + { 500, 1000000 }, + /* 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 }, +}; + +#define NUM_TIMEBASE ARRAY_SIZE(timebases) +#define NUM_VDIV ARRAY_SIZE(vdivs) + +static const char *trigger_sources[] = { + "CH1", + "CH2", + "EXT", + "AC Line", +}; + +static const char *coupling[] = { + "AC", + "DC", + "GND", +}; + +static const char *supported_models[] = { + "DS2072", + "DS2102", + "DS2202", +}; + +SR_PRIV struct sr_dev_driver rigol_ds2xx2_driver_info; +static struct sr_dev_driver *di = &rigol_ds2xx2_driver_info; + +static void clear_helper(void *priv) +{ + struct dev_context *devc; + + devc = priv; + + g_free(devc->coupling[0]); + g_free(devc->coupling[1]); + g_free(devc->trigger_source); + g_free(devc->trigger_slope); +} + +static int dev_clear(void) +{ + return std_dev_clear(di, clear_helper); +} + +static int set_cfg(const struct sr_dev_inst *sdi, const char *format, ...) +{ + va_list args; + char buf[256]; + + va_start(args, format); + vsnprintf(buf, 255, format, args); + va_end(args); + if (rigol_ds2xx2_send(sdi, buf) != SR_OK) + return SR_ERR; + + /* When setting a bunch of parameters in a row, the DS1052E scrambles + * some of them unless there is at least 100ms delay in between. */ + /* Not sure whether this applies to DS2xx2 too - left in for now */ + sr_spew("delay %dms", 100); + g_usleep(100000); + + return SR_OK; +} + +static int init(struct sr_context *sr_ctx) +{ + return std_init(sr_ctx, di, LOG_PREFIX); +} + +static int probe_port(const char *port, GSList **devices) +{ + struct dev_context *devc; + struct sr_dev_inst *sdi; + struct sr_serial_dev_inst *serial; + struct sr_probe *probe; + unsigned int i; + int len, num_tokens; + gboolean matched; + const char *manufacturer, *model, *version; + char buf[256]; + gchar **tokens; + + *devices = NULL; + if (!(serial = sr_serial_dev_inst_new(port, NULL))) + return SR_ERR_MALLOC; + + if (serial_open(serial, SERIAL_RDWR) != SR_OK) + return SR_ERR; + len = serial_write(serial, "*IDN?", 5); + len = serial_read(serial, buf, sizeof(buf)); + if (serial_close(serial) != SR_OK) + return SR_ERR; + + sr_serial_dev_inst_free(serial); + + if (len == 0) + return SR_ERR_NA; + + buf[len] = 0; + tokens = g_strsplit(buf, ",", 0); + sr_dbg("response: %s [%s]", port, buf); + + for (num_tokens = 0; tokens[num_tokens] != NULL; num_tokens++); + + if (num_tokens < 4) { + g_strfreev(tokens); + return SR_ERR_NA; + } + + manufacturer = tokens[0]; + model = tokens[1]; + version = tokens[3]; + + if (strcasecmp(manufacturer, "Rigol Technologies")) { + g_strfreev(tokens); + return SR_ERR_NA; + } + + matched = FALSE; + for (i = 0; i < ARRAY_SIZE(supported_models); i++) { + if (!strcmp(model, supported_models[i])) { + matched = TRUE; + break; + } + } + + if (!matched || !(sdi = sr_dev_inst_new(0, SR_ST_ACTIVE, + manufacturer, model, version))) { + g_strfreev(tokens); + return SR_ERR_NA; + } + + g_strfreev(tokens); + + if (!(sdi->conn = sr_serial_dev_inst_new(port, NULL))) + return SR_ERR_MALLOC; + sdi->driver = di; + sdi->inst_type = SR_INST_SERIAL; + + if (!(devc = g_try_malloc0(sizeof(struct dev_context)))) + return SR_ERR_MALLOC; + devc->limit_frames = 0; + + for (i = 0; i < 2; i++) { + if (!(probe = sr_probe_new(i, SR_PROBE_ANALOG, TRUE, + i == 0 ? "CH1" : "CH2"))) + return SR_ERR_MALLOC; + sdi->probes = g_slist_append(sdi->probes, probe); + } + + sdi->priv = devc; + + *devices = g_slist_append(NULL, sdi); + + return SR_OK; +} + +static GSList *scan(GSList *options) +{ + struct drv_context *drvc; + struct sr_config *src; + GSList *l, *devices; + GDir *dir; + int ret; + const gchar *dev_name; + gchar *port = NULL; + + drvc = di->priv; + + for (l = options; l; l = l->next) { + src = l->data; + if (src->key == SR_CONF_CONN) { + port = (char *)g_variant_get_string(src->data, NULL); + break; + } + } + + devices = NULL; + if (port) { + if (probe_port(port, &devices) == SR_ERR_MALLOC) + return NULL; + } else { + if (!(dir = g_dir_open("/sys/class/usbmisc/", 0, NULL))) + if (!(dir = g_dir_open("/sys/class/usb/", 0, NULL))) + return NULL; + while ((dev_name = g_dir_read_name(dir))) { + if (strncmp(dev_name, "usbtmc", 6)) + continue; + port = g_strconcat("/dev/", dev_name, NULL); + ret = probe_port(port, &devices); + g_free(port); + if (ret == SR_ERR_MALLOC) { + g_dir_close(dir); + return NULL; + } + } + g_dir_close(dir); + } + + /* Tack a copy of the newly found devices onto the driver list. */ + l = g_slist_copy(devices); + drvc->instances = g_slist_concat(drvc->instances, l); + + return devices; +} + +static GSList *dev_list(void) +{ + return ((struct drv_context *)(di->priv))->instances; +} + +static int dev_open(struct sr_dev_inst *sdi) +{ + + if (serial_open(sdi->conn, SERIAL_RDWR) != SR_OK) + return SR_ERR; + + if (rigol_ds2xx2_get_dev_cfg(sdi) != SR_OK) + return SR_ERR; + + sdi->status = SR_ST_ACTIVE; + + return SR_OK; +} + +static int dev_close(struct sr_dev_inst *sdi) +{ + struct sr_serial_dev_inst *serial; + + serial = sdi->conn; + if (serial && serial->fd != -1) { + serial_close(serial); + sdi->status = SR_ST_INACTIVE; + } + + return SR_OK; +} + +static int cleanup(void) +{ + return dev_clear(); +} + +static int config_get(int id, GVariant **data, const struct sr_dev_inst *sdi) +{ + + (void)sdi; + + switch (id) { + case SR_CONF_NUM_TIMEBASE: + *data = g_variant_new_int32(NUM_TIMEBASE); + break; + case SR_CONF_NUM_VDIV: + *data = g_variant_new_int32(NUM_VDIV); + break; + default: + return SR_ERR_NA; + } + + return SR_OK; +} + +static int config_set(int id, GVariant *data, const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + uint64_t tmp_u64, p, q; + double t_dbl; + unsigned int i; + int ret; + const char *tmp_str; + + devc = sdi->priv; + + if (sdi->status != SR_ST_ACTIVE) + return SR_ERR_DEV_CLOSED; + + ret = SR_OK; + switch (id) { + case SR_CONF_LIMIT_FRAMES: + devc->limit_frames = g_variant_get_uint64(data); + break; + case SR_CONF_TRIGGER_SLOPE: + tmp_u64 = g_variant_get_uint64(data); + if (tmp_u64 != 0 && tmp_u64 != 1) + return SR_ERR; + g_free(devc->trigger_slope); + devc->trigger_slope = g_strdup(tmp_u64 ? "POS" : "NEG"); + ret = set_cfg(sdi, ":TRIG:EDGE:SLOP %s", devc->trigger_slope); + break; + case SR_CONF_HORIZ_TRIGGERPOS: + t_dbl = g_variant_get_double(data); + if (t_dbl < 0.0 || t_dbl > 1.0) + return SR_ERR; + devc->horiz_triggerpos = t_dbl; + /* We have the trigger offset as a percentage of the frame, but + * need to express this in seconds. */ + t_dbl = -(devc->horiz_triggerpos - 0.5) * devc->timebase * NUM_TIMEBASE; + ret = set_cfg(sdi, ":TIM:OFFS %.6f", t_dbl); + break; + case SR_CONF_TIMEBASE: + g_variant_get(data, "(tt)", &p, &q); + for (i = 0; i < ARRAY_SIZE(timebases); i++) { + if (timebases[i][0] == p && timebases[i][1] == q) { + devc->timebase = (float)p / q; + ret = set_cfg(sdi, ":TIM:SCAL %.9f", devc->timebase); + break; + } + } + if (i == ARRAY_SIZE(timebases)) + ret = SR_ERR_ARG; + break; + case SR_CONF_TRIGGER_SOURCE: + tmp_str = g_variant_get_string(data, NULL); + for (i = 0; i < ARRAY_SIZE(trigger_sources); i++) { + if (!strcmp(trigger_sources[i], tmp_str)) { + g_free(devc->trigger_source); + devc->trigger_source = g_strdup(trigger_sources[i]); + if (!strcmp(devc->trigger_source, "AC Line")) + tmp_str = "ACL"; + else if (!strcmp(devc->trigger_source, "CH1")) + tmp_str = "CHAN1"; + else if (!strcmp(devc->trigger_source, "CH2")) + tmp_str = "CHAN2"; + ret = set_cfg(sdi, ":TRIG:EDGE:SOUR %s", tmp_str); + break; + } + } + if (i == ARRAY_SIZE(trigger_sources)) + ret = SR_ERR_ARG; + break; + case SR_CONF_VDIV: + g_variant_get(data, "(tt)", &p, &q); + for (i = 0; i < ARRAY_SIZE(vdivs); i++) { + if (vdivs[i][0] != p || vdivs[i][1] != q) + continue; + devc->vdiv[0] = devc->vdiv[1] = (float)p / q; + set_cfg(sdi, ":CHAN1:SCAL %.3f", devc->vdiv[0]); + ret = set_cfg(sdi, ":CHAN2:SCAL %.3f", devc->vdiv[1]); + break; + } + if (i == ARRAY_SIZE(vdivs)) + ret = SR_ERR_ARG; + break; + case SR_CONF_COUPLING: + /* TODO: Not supporting coupling per channel yet. */ + tmp_str = g_variant_get_string(data, NULL); + for (i = 0; i < ARRAY_SIZE(coupling); i++) { + if (!strcmp(tmp_str, coupling[i])) { + g_free(devc->coupling[0]); + g_free(devc->coupling[1]); + devc->coupling[0] = g_strdup(coupling[i]); + devc->coupling[1] = g_strdup(coupling[i]); + set_cfg(sdi, ":CHAN1:COUP %s", devc->coupling[0]); + ret = set_cfg(sdi, ":CHAN2:COUP %s", devc->coupling[1]); + break; + } + } + if (i == ARRAY_SIZE(coupling)) + ret = SR_ERR_ARG; + break; + default: + ret = SR_ERR_NA; + break; + } + + return ret; +} + +static int config_list(int key, GVariant **data, const struct sr_dev_inst *sdi) +{ + GVariant *tuple, *rational[2]; + GVariantBuilder gvb; + unsigned int i; + + switch (key) { + case SR_CONF_SCAN_OPTIONS: + *data = g_variant_new_fixed_array(G_VARIANT_TYPE_INT32, + hwopts, ARRAY_SIZE(hwopts), sizeof(int32_t)); + break; + case SR_CONF_DEVICE_OPTIONS: + *data = g_variant_new_fixed_array(G_VARIANT_TYPE_INT32, + hwcaps, ARRAY_SIZE(hwcaps), sizeof(int32_t)); + break; + case SR_CONF_COUPLING: + *data = g_variant_new_strv(coupling, ARRAY_SIZE(coupling)); + break; + case SR_CONF_VDIV: + g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY); + for (i = 0; i < ARRAY_SIZE(vdivs); i++) { + rational[0] = g_variant_new_uint64(vdivs[i][0]); + rational[1] = g_variant_new_uint64(vdivs[i][1]); + tuple = g_variant_new_tuple(rational, 2); + g_variant_builder_add_value(&gvb, tuple); + } + *data = g_variant_builder_end(&gvb); + break; + case SR_CONF_TIMEBASE: + g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY); + for (i = 0; i < ARRAY_SIZE(timebases); i++) { + rational[0] = g_variant_new_uint64(timebases[i][0]); + rational[1] = g_variant_new_uint64(timebases[i][1]); + tuple = g_variant_new_tuple(rational, 2); + g_variant_builder_add_value(&gvb, tuple); + } + *data = g_variant_builder_end(&gvb); + break; + case SR_CONF_TRIGGER_SOURCE: + if (!sdi || !sdi->priv) + /* Can't know this until we have the exact model. */ + return SR_ERR_ARG; + *data = g_variant_new_strv(trigger_sources, 4); + break; + default: + return SR_ERR_NA; + } + + return SR_OK; +} + +static int dev_acquisition_start(const struct sr_dev_inst *sdi, void *cb_data) +{ + struct sr_serial_dev_inst *serial; + struct dev_context *devc; + struct sr_probe *probe; + GSList *l; + char cmd[256]; + + if (sdi->status != SR_ST_ACTIVE) + return SR_ERR_DEV_CLOSED; + + serial = sdi->conn; + devc = sdi->priv; + + for (l = sdi->probes; l; l = l->next) { + probe = l->data; + sr_dbg("handling probe %s", probe->name); + if (probe->type == SR_PROBE_ANALOG) { + if (probe->enabled) + devc->enabled_analog_probes = g_slist_append( + devc->enabled_analog_probes, probe); + if (probe->enabled != devc->analog_channels[probe->index]) { + /* Enabled channel is currently disabled, or vice versa. */ + sprintf(cmd, ":CHAN%d:DISP %s", probe->index + 1, + probe->enabled ? "ON" : "OFF"); + if (rigol_ds2xx2_send(sdi, cmd) != SR_OK) + return SR_ERR; + } + } + } + if (!devc->enabled_analog_probes) + return SR_ERR; + + sr_source_add(serial->fd, G_IO_IN, 50, rigol_ds2xx2_receive, (void *)sdi); + + /* Send header packet to the session bus. */ + std_session_send_df_header(cb_data, LOG_PREFIX); + + /* Fetch the first frame. */ + if (devc->enabled_analog_probes) { + devc->channel_frame = devc->enabled_analog_probes->data; + if (rigol_ds2xx2_send(sdi, ":WAV:SOUR CHAN%d", + devc->channel_frame->index + 1) != SR_OK) + return SR_ERR; + if (rigol_ds2xx2_send(sdi, ":WAV:MODE NORM") != SR_OK) + return SR_ERR; + if (rigol_ds2xx2_send(sdi, ":WAV:DATA?") != SR_OK) + return SR_ERR; + } + + devc->num_frame_bytes = 0; + + return SR_OK; +} + +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; + + devc = sdi->priv; + + if (sdi->status != SR_ST_ACTIVE) { + sr_err("Device inactive, can't stop acquisition."); + return SR_ERR; + } + + g_slist_free(devc->enabled_analog_probes); + devc->enabled_analog_probes = NULL; + serial = sdi->conn; + sr_source_remove(serial->fd); + + return SR_OK; +} + +SR_PRIV struct sr_dev_driver rigol_ds2xx2_driver_info = { + .name = "rigol-ds2xx2", + .longname = "Rigol DS2xx2", + .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, +}; + +/* Local Variables: */ +/* c-basic-offset: 8 */ +/* End: */ diff -urN libsigrok.org/hardware/rigol-ds2xx2/Makefile.am libsigrok/hardware/rigol-ds2xx2/Makefile.am --- libsigrok.org/hardware/rigol-ds2xx2/Makefile.am 1970-01-01 01:00:00.000000000 +0100 +++ libsigrok/hardware/rigol-ds2xx2/Makefile.am 2013-10-20 19:18:46.677498253 +0200 @@ -0,0 +1,33 @@ +## +## This file is part of the libsigrok project. +## +## Copyright (C) 2012 Martin Ling <mar...@ea...>, +## +## 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_RIGOL_DS2XX2 + +# Local lib, this is NOT meant to be installed! +noinst_LTLIBRARIES = libsigrok_hw_rigol_ds2xx2.la + +libsigrok_hw_rigol_ds2xx2_la_SOURCES = \ + api.c \ + protocol.c \ + protocol.h + +libsigrok_hw_rigol_ds2xx2_la_CFLAGS = \ + -I$(top_srcdir) + +endif diff -urN libsigrok.org/hardware/rigol-ds2xx2/protocol.c libsigrok/hardware/rigol-ds2xx2/protocol.c --- libsigrok.org/hardware/rigol-ds2xx2/protocol.c 1970-01-01 01:00:00.000000000 +0100 +++ libsigrok/hardware/rigol-ds2xx2/protocol.c 2013-10-20 18:41:34.326888083 +0200 @@ -0,0 +1,335 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2012 Martin Ling <mar...@ea...> + * Copyright (C) 2013 Bert Vermeulen <be...@bi...> + * Copyright (C) 2013 Mathias Grimmberger <mg...@za...> + * + * 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 <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <math.h> +#include <glib.h> +#include "libsigrok.h" +#include "libsigrok-internal.h" +#include "protocol.h" + +/* Based on protocol.c from Rigol DS1xx2 driver. */ + +/* + * Each waveform block from the scope has a header of 11 bytes. + * Format: #9xxxxxxxxxxx (example: #900000001400) + * Where "#9" is constant and the rest gives the sample count + * as a decimal number. + * + * Each waveform block also has a trailing linefeed, need + * to discard that when implementing raw memory read. + */ +#define WAVEFORM_HEADER_SIZE 11 + +SR_PRIV void rigol_ds2xx2_receive_header(struct sr_serial_dev_inst *serial) +{ + char buf[WAVEFORM_HEADER_SIZE + 1]; + int len; + + /* + * For now just consume the header. Later this needs to properly + * handle the returned sample count and do error handling. + */ + len = serial_read(serial, buf, WAVEFORM_HEADER_SIZE); + buf[WAVEFORM_HEADER_SIZE] = '\0'; + sr_dbg("Received %d bytes of waveform header: %s.", len, buf); +} + +SR_PRIV int rigol_ds2xx2_receive(int fd, int revents, void *cb_data) +{ + struct sr_dev_inst *sdi; + struct sr_serial_dev_inst *serial; + struct dev_context *devc; + struct sr_datafeed_packet packet; + struct sr_datafeed_analog analog; + unsigned char buf[ANALOG_WAVEFORM_SIZE]; + double vdiv, offset; + float data[ANALOG_WAVEFORM_SIZE]; + int len, i, vref; + struct sr_probe *probe; + + (void)fd; + + if (!(sdi = cb_data)) + return TRUE; + + if (!(devc = sdi->priv)) + return TRUE; + + serial = sdi->conn; + + if (revents == G_IO_IN) { + if (devc->num_frame_bytes == 0) { + /* Consume waveform header. */ + rigol_ds2xx2_receive_header(serial); + } + + probe = devc->channel_frame; + len = serial_read(serial, buf, ANALOG_WAVEFORM_SIZE - devc->num_frame_bytes); + sr_dbg("Received %d bytes.", len); + if (len == -1) + return TRUE; + + if (devc->num_frame_bytes == 0) { + /* Start of a new frame. */ + packet.type = SR_DF_FRAME_BEGIN; + sr_session_send(sdi, &packet); + } + + vdiv = devc->vdiv[probe->index] / 25.6; + vref = devc->vert_reference[probe->index]; + offset = devc->vert_offset[probe->index]; + for (i = 0; i < len; i++) { + data[i] = ((int)buf[i] - vref) * vdiv - offset; + } + analog.probes = g_slist_append(NULL, probe); + analog.num_samples = len; + analog.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); + + if (len != ANALOG_WAVEFORM_SIZE) + /* Don't have the whole frame yet. */ + return TRUE; + + /* End of the frame. */ + packet.type = SR_DF_FRAME_END; + sr_session_send(sdi, &packet); + devc->num_frame_bytes = 0; + + if (devc->enabled_analog_probes + && devc->channel_frame == devc->enabled_analog_probes->data + && devc->enabled_analog_probes->next != NULL) { + /* We got the frame for the first analog channel, but + * there's a second analog channel. */ + devc->channel_frame = devc->enabled_analog_probes->next->data; + rigol_ds2xx2_send(sdi, ":WAV:SOUR CHAN%d", + devc->channel_frame->index + 1); + rigol_ds2xx2_send(sdi, ":WAV:MODE NORM"); + rigol_ds2xx2_send(sdi, ":WAV:DATA?"); + } else { + /* Done with both analog channels in this frame. */ + if (++devc->num_frames == devc->limit_frames) { + /* End of last frame. */ + sdi->driver->dev_acquisition_stop(sdi, cb_data); + } else { + /* Get the next frame, starting with the first analog channel. */ + if (devc->enabled_analog_probes) { + devc->channel_frame = devc->enabled_analog_probes->data; + rigol_ds2xx2_send(sdi, ":WAV:SOUR CHAN%d", + devc->channel_frame->index + 1); + rigol_ds2xx2_send(sdi, ":WAV:MODE NORM"); + rigol_ds2xx2_send(sdi, ":WAV:DATA?"); + } + } + } + } + + return TRUE; +} + +SR_PRIV int rigol_ds2xx2_send(const struct sr_dev_inst *sdi, const char *format, ...) +{ + va_list args; + char buf[256]; + int len, out, ret; + + va_start(args, format); + len = vsnprintf(buf, 255, format, args); + va_end(args); + strcat(buf, "\n"); + len++; + out = serial_write(sdi->conn, buf, len); + buf[len - 1] = '\0'; + if (out != len) { + sr_dbg("Only sent %d/%d bytes of '%s'.", out, len, buf); + ret = SR_ERR; + } else { + sr_spew("Sent '%s'.", buf); + ret = SR_OK; + } + + return ret; +} + +static int get_cfg(const struct sr_dev_inst *sdi, char *cmd, char *reply) +{ + int len; + + if (rigol_ds2xx2_send(sdi, cmd) != SR_OK) + return SR_ERR; + + if ((len = serial_read(sdi->conn, reply, 255)) < 0) + return SR_ERR; + reply[len] = '\0'; + /* get rid of trailing linefeed */ + if (len >= 1 && reply[len-1] == '\n') + { + reply[len-1] = '\0'; + } + sr_spew("Received '%s'.", reply); + + return SR_OK; +} + +static int get_cfg_int(const struct sr_dev_inst *sdi, char *cmd, int *i) +{ + char buf[256], *e; + long tmp; + + if (get_cfg(sdi, cmd, buf) != SR_OK) + return SR_ERR; + errno = 0; + tmp = strtol(buf, &e, 10); + if (e == buf) { + sr_dbg("failed to parse response to '%s': '%s'", cmd, buf); + return SR_ERR; + } + if (errno) { + sr_dbg("failed to parse response to '%s': '%s', numerical overflow", cmd, buf); + return SR_ERR; + } + if (tmp > INT_MAX || tmp < INT_MIN) { + sr_dbg("failed to parse response to '%s': '%s', value to large/small", cmd, buf); + return SR_ERR; + } + + *i =(int)tmp; + return SR_OK; +} + +static int get_cfg_float(const struct sr_dev_inst *sdi, char *cmd, float *f) +{ + char buf[256], *e; + + if (get_cfg(sdi, cmd, buf) != SR_OK) + return SR_ERR; + *f = strtof(buf, &e); + if (e == buf || (fpclassify(*f) & (FP_ZERO | FP_NORMAL)) == 0) { + sr_dbg("failed to parse response to '%s': '%s'", cmd, buf); + return SR_ERR; + } + + return SR_OK; +} + +static int get_cfg_string(const struct sr_dev_inst *sdi, char *cmd, char **buf) +{ + + if (!(*buf = g_try_malloc0(256))) + return SR_ERR_MALLOC; + + if (get_cfg(sdi, cmd, *buf) != SR_OK) + return SR_ERR; + + return SR_OK; +} + +SR_PRIV int rigol_ds2xx2_get_dev_cfg(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + char *t_s; + + devc = sdi->priv; + + /* Analog channel state. */ + if (get_cfg_string(sdi, ":CHAN1:DISP?", &t_s) != SR_OK) + return SR_ERR; + devc->analog_channels[0] = !strcmp(t_s, "1") ? TRUE : FALSE; + g_free(t_s); + if (get_cfg_string(sdi, ":CHAN2:DISP?", &t_s) != SR_OK) + return SR_ERR; + devc->analog_channels[1] = !strcmp(t_s, "1") ? TRUE : FALSE; + g_free(t_s); + sr_dbg("Current analog channel state CH1 %s CH2 %s", + devc->analog_channels[0] ? "on" : "off", + devc->analog_channels[1] ? "on" : "off"); + + /* Timebase. */ + if (get_cfg_float(sdi, ":TIM:SCAL?", &devc->timebase) != SR_OK) + return SR_ERR; + sr_dbg("Current timebase %g", devc->timebase); + + /* Vertical gain. */ + if (get_cfg_float(sdi, ":CHAN1:SCAL?", &devc->vdiv[0]) != SR_OK) + return SR_ERR; + if (get_cfg_float(sdi, ":CHAN2:SCAL?", &devc->vdiv[1]) != SR_OK) + return SR_ERR; + sr_dbg("Current vertical gain CH1 %g CH2 %g", devc->vdiv[0], devc->vdiv[1]); + + /* Vertical reference - not certain if this is the place to read it. */ + if (rigol_ds2xx2_send(sdi, ":WAV:SOUR CHAN1") != SR_OK) + return SR_ERR; + if (get_cfg_int(sdi, ":WAV:YREF?", &devc->vert_reference[0]) != SR_OK) + return SR_ERR; + if (rigol_ds2xx2_send(sdi, ":WAV:SOUR CHAN2") != SR_OK) + return SR_ERR; + if (get_cfg_int(sdi, ":WAV:YREF?", &devc->vert_reference[1]) != SR_OK) + return SR_ERR; + sr_dbg("Current vertical reference CH1 %d CH2 %d", devc->vert_reference[0], + devc->vert_reference[1]); + + /* Vertical offset. */ + if (get_cfg_float(sdi, ":CHAN1:OFFS?", &devc->vert_offset[0]) != SR_OK) + return SR_ERR; + if (get_cfg_float(sdi, ":CHAN2:OFFS?", &devc->vert_offset[1]) != SR_OK) + return SR_ERR; + sr_dbg("Current vertical offset CH1 %g CH2 %g", devc->vert_offset[0], + devc->vert_offset[1]); + + /* Coupling. */ + if (get_cfg_string(sdi, ":CHAN1:COUP?", &devc->coupling[0]) != SR_OK) + return SR_ERR; + if (get_cfg_string(sdi, ":CHAN2:COUP?", &devc->coupling[1]) != SR_OK) + return SR_ERR; + sr_dbg("Current coupling CH1 %s CH2 %s", devc->coupling[0], + devc->coupling[1]); + + /* Trigger source. */ + if (get_cfg_string(sdi, ":TRIG:EDGE:SOUR?", &devc->trigger_source) != SR_OK) + return SR_ERR; + sr_dbg("Current trigger source %s", devc->trigger_source); + + /* Horizontal trigger position. */ + if (get_cfg_float(sdi, ":TIM:OFFS?", &devc->horiz_triggerpos) != SR_OK) + return SR_ERR; + sr_dbg("Current horizontal trigger position %g", devc->horiz_triggerpos); + + /* Trigger slope. */ + if (get_cfg_string(sdi, ":TRIG:EDGE:SLOP?", &devc->trigger_slope) != SR_OK) + return SR_ERR; + sr_dbg("Current trigger slope %s", devc->trigger_slope); + + return SR_OK; +} + +/* Local Variables: */ +/* c-basic-offset: 8 */ +/* End: */ diff -urN libsigrok.org/hardware/rigol-ds2xx2/protocol.h libsigrok/hardware/rigol-ds2xx2/protocol.h --- libsigrok.org/hardware/rigol-ds2xx2/protocol.h 1970-01-01 01:00:00.000000000 +0100 +++ libsigrok/hardware/rigol-ds2xx2/protocol.h 2013-10-20 19:19:16.767886421 +0200 @@ -0,0 +1,67 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2012 Martin Ling <mar...@ea...> + * Copyright (C) 2013 Bert Vermeulen <be...@bi...> + * + * 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_RIGOL_DS2XX2_PROTOCOL_H +#define LIBSIGROK_HARDWARE_RIGOL_DS2XX2_PROTOCOL_H + +#include <stdint.h> +#include "libsigrok.h" +#include "libsigrok-internal.h" + +/* Message logging helpers with subsystem-specific prefix string. */ +#define LOG_PREFIX "rigol-ds2xx2: " +#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) + +#define ANALOG_WAVEFORM_SIZE 1400 + +/** Private, per-device-instance driver context. */ +struct dev_context { + /* Acquisition settings */ + GSList *enabled_analog_probes; + uint64_t limit_frames; + void *cb_data; + + /* Device settings */ + gboolean analog_channels[2]; + float timebase; + float vdiv[2]; + int vert_reference[2]; + float vert_offset[2]; + char *trigger_source; + float horiz_triggerpos; + char *trigger_slope; + char *coupling[2]; + + /* Operational state */ + uint64_t num_frames; + uint64_t num_frame_bytes; + struct sr_probe *channel_frame; +}; + +SR_PRIV int rigol_ds2xx2_receive(int fd, int revents, void *cb_data); +SR_PRIV int rigol_ds2xx2_send(const struct sr_dev_inst *sdi, const char *format, ...); +SR_PRIV int rigol_ds2xx2_get_dev_cfg(const struct sr_dev_inst *sdi); + +#endif diff -urN libsigrok.org/hwdriver.c libsigrok/hwdriver.c --- libsigrok.org/hwdriver.c 2013-10-18 22:18:36.000000000 +0200 +++ libsigrok/hwdriver.c 2013-10-19 00:21:12.116947533 +0200 @@ -145,6 +145,9 @@ #ifdef HAVE_HW_RIGOL_DS1XX2 extern SR_PRIV struct sr_dev_driver rigol_ds1xx2_driver_info; #endif +#ifdef HAVE_HW_RIGOL_DS2XX2 +extern SR_PRIV struct sr_dev_driver rigol_ds2xx2_driver_info; +#endif #ifdef HAVE_HW_SALEAE_LOGIC16 extern SR_PRIV struct sr_dev_driver saleae_logic16_driver_info; #endif @@ -259,6 +262,9 @@ #ifdef HAVE_HW_RIGOL_DS1XX2 &rigol_ds1xx2_driver_info, #endif +#ifdef HAVE_HW_RIGOL_DS2XX2 + &rigol_ds2xx2_driver_info, +#endif #ifdef HAVE_HW_SALEAE_LOGIC16 &saleae_logic16_driver_info, #endif diff -urN libsigrok.org/README.devices libsigrok/README.devices --- libsigrok.org/README.devices 2013-10-09 23:51:06.000000000 +0200 +++ libsigrok/README.devices 2013-10-20 18:58:03.034784924 +0200 @@ -66,6 +66,7 @@ - mic-985xx - openbench-logic-sniffer - rigol-ds1xx2 + - rigol-ds2xx2 - serial-dmm - tondaj-sl-814 - uni-t-dmm @@ -115,6 +116,7 @@ - kecheng-kc-330b - lascar-el-usb - rigol-ds1xx2 + - rigol-ds2xx2 - saleae-logic16 - uni-t-dmm - uni-t-ut32x @@ -353,3 +355,9 @@ library (which uses libusb) soon, which will fix all these issues and make the driver portable at the same time. + +Rigol DS2xx2 oscilloscopes +-------------------------- + +Because the 'rigol-ds2xx2' driver is based on the 'rigol-ds1xx2' driver it +shares its dependency on the Linux usbtmc kernel driver. |
From: Martin L. <mar...@ea...> - 2013-10-20 20:50:14
|
Hi Mathias, I'm glad to see that you've been able to adapt my DS1xx2 driver to support the DS2xx2 series. Looking through the code, it seems that the differences between the two are relatively minor and as such there is a lot of code duplicated between the ds1xx2 and ds2xx2 drivers after applying your patch. I think it would be better for future maintenance if we could merge the two into a single "dsxxx2" driver that supported both series. I could help with this work and test on the two DS1xx2 scopes at my lab, if you could test against a DSxx2. Looking through the diff between ds1xx2 and ds2xx2, it looks like the main changes required to support both would be: - Adjusting the list of supported timebase and vdiv settings according to the actual model of scope connected (which we ought to be doing anyway, as the available timebases vary between the bandwidth options even on the DS1xx2 series, and the vdiv settings depend on the probe gain). - Omitting the digital channels for the DS2xx2 series (simple, since this is already done for the DS1xx2 models that don't have them). - Adjusting the waveform readout protocol for the DS2xx2 series. - Adding the vertical reference support. Have I missed anything? Martin On Sun, Oct 20, 2013 at 09:41:18PM +0200, Mathias Grimmberger wrote: > > > Hi everybody, > > I cobbled together a first version of hardware support for the Rigol > DS2xx2 series oscilloscopes (models DS2072, DS2102, DS2202). > > It is heavily based on the rigol-ds1xx2 code. Because of this it shares > its quirks. > > Grabbing the displayed samples (1400 per channel) works when the scope > is running, when stopped this only works if the horizontal settings > haven't been changed. > > > Enjoy, > > MGri |
From: Mathias G. <mg...@za...> - 2013-10-21 18:42:12
|
Hi Martin, Martin Ling writes: > > I think it would be better for future maintenance if we could merge the > two into a single "dsxxx2" driver that supported both series. The thought crossed my mind too, but then I took the fast, easy and safe route... > I could help with this work and test on the two DS1xx2 scopes at my > lab, if you could test against a DSxx2. OK, thank you. > Looking through the diff between ds1xx2 and ds2xx2, it looks like the > main changes required to support both would be: > > - Adjusting the list of supported timebase and vdiv settings according to > the actual model of scope connected (which we ought to be doing anyway, > as the available timebases vary between the bandwidth options even on > the DS1xx2 series, and the vdiv settings depend on the probe gain). > > - Omitting the digital channels for the DS2xx2 series (simple, since > this is already done for the DS1xx2 models that don't have them). > > - Adjusting the waveform readout protocol for the DS2xx2 series. > > - Adding the vertical reference support. > > Have I missed anything? I want to implement some more of the stuff the DS2xx2 can do, specifically: - reading the complete sample memory when the scope is stopped, - reading samples taken in HiRes mode (12 bits per sample), - at least check out what is there in peak detect aquisition mode. For that I will keep the code separate for now. When I have understood how all this works a unified driver can be started. BTW, a glance through the programming manuals of the DS4000 and DS6000 series scopes shows them using the same command structure as the DS2000 series. I guess we can have a single driver covering the DS1xx2, DS2000, DS4000 and DS6000 series. Likely DS1000Z series too, but I have no programming manual for that yet. MGri |
From: Bert V. <be...@bi...> - 2013-10-21 23:09:51
|
On 10/21/2013 07:34 PM, Mathias Grimmberger wrote: > BTW, a glance through the programming manuals of the DS4000 and DS6000 > series scopes shows them using the same command structure as the DS2000 > series. I guess we can have a single driver covering the DS1xx2, DS2000, > DS4000 and DS6000 series. Likely DS1000Z series too, but I have no > programming manual for that yet. A Rigol DS* driver sounds great. The devices it supports don't have to be near-identical in order to group them -- some DMM drivers supports of different devices, with significant differences in the protocol. But it still makes sense to group them if they have _some_ stuff in common, and the model can be identified by protocol. -- Bert Vermeulen be...@bi... |
From: Martin L. <mar...@ea...> - 2013-10-22 15:02:11
|
On Mon, Oct 21, 2013 at 08:34:01PM +0200, Mathias Grimmberger wrote: > > I want to implement some more of the stuff the DS2xx2 can do, > specifically: > > - reading the complete sample memory when the scope is stopped, > > - reading samples taken in HiRes mode (12 bits per sample), > > - at least check out what is there in peak detect aquisition mode. > > For that I will keep the code separate for now. Apart from the 12-bit mode these are things the DS1xx2 models can do too, so if you make some progress I will see about backporting it. In the meantime I will have a go at merging your changes so far into the existing driver, and sending you a patch to test. Martin |