From: Uwe Hermann Date: Sat, 27 Oct 2012 20:41:50 +0000 (+0200) Subject: Initial support for UNI-T DMMs. X-Git-Tag: dsupstream~622 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=79081ec80c79801c439a1a3756cf5426af0739b8;p=libsigrok.git Initial support for UNI-T DMMs. This is not yet fully finished, but works for most use-cases. Tested with a UNI-T UT61D using the UT-D04 USB/HID cable (new version). --- diff --git a/configure.ac b/configure.ac index 95ea96a7..d6ff55de 100644 --- a/configure.ac +++ b/configure.ac @@ -196,6 +196,15 @@ if test "x$HW_TEKPOWER_DMM" = "xyes"; then AC_DEFINE(HAVE_HW_TEKPOWER_DMM, 1, [TekPower DMM support]) fi +AC_ARG_ENABLE(uni-t-dmm, AC_HELP_STRING([--enable-uni-t-dmm], + [enable UNI-T DMM support [default=yes]]), + [HW_UNI_T_DMM="$enableval"], + [HW_UNI_T_DMM=yes]) +AM_CONDITIONAL(HW_UNI_T_DMM, test x$HW_UNI_T_DMM = xyes) +if test "x$HW_UNI_T_DMM" = "xyes"; then + AC_DEFINE(HAVE_HW_UNI_T_DMM, 1, [UNI-T DMM support]) +fi + AC_ARG_ENABLE(zeroplus-logic-cube, AC_HELP_STRING([--enable-zeroplus-logic-cube], [enable ZEROPLUS Logic Cube support [default=yes]]), @@ -273,13 +282,13 @@ if test "x$LA_ALSA" != xno; then SR_PKGLIBS="$SR_PKGLIBS alsa"]) fi -# EZUSB FX2 firmware helpers only needed for some hardware drivers +# EZUSB FX2 firmware helper code is only needed for some hardware drivers. AM_CONDITIONAL(NEED_EZUSB, \ test "x$LA_FX2LAFW" != xno \ -o "x$HW_HANTEK_DSO" != xno \ ) -# Serial port helpers only needed for some hardware drivers +# Serial port helper code is only needed for some hardware drivers. AM_CONDITIONAL(NEED_SERIAL, \ test "x$LA_OLS" != xno \ -o "x$HW_AGILENT_DMM" != xno \ @@ -327,6 +336,7 @@ AC_CONFIG_FILES([Makefile version.h hardware/Makefile hardware/asix-sigma/Makefile hardware/chronovu-la8/Makefile hardware/common/Makefile + hardware/common/dmm/Makefile hardware/demo/Makefile hardware/fluke-dmm/Makefile hardware/fx2lafw/Makefile @@ -336,6 +346,7 @@ AC_CONFIG_FILES([Makefile version.h hardware/Makefile hardware/openbench-logic-sniffer/Makefile hardware/radioshack-dmm/Makefile hardware/tekpower-dmm/Makefile + hardware/uni-t-dmm/Makefile hardware/zeroplus-logic-cube/Makefile input/Makefile output/Makefile @@ -380,6 +391,7 @@ echo " - Link MSO-19..................... $LA_LINK_MSO19" echo " - Openbench Logic Sniffer......... $LA_OLS" echo " - Radioshack DMM.................. $HW_RADIOSHACK_DMM" echo " - TekPower DMM.................... $HW_TEKPOWER_DMM" +echo " - UNI-T DMM....................... $HW_UNI_T_DMM" echo " - ZEROPLUS Logic Cube............. $LA_ZEROPLUS_LOGIC_CUBE" echo diff --git a/hardware/Makefile.am b/hardware/Makefile.am index c6e2ae80..66b31fcf 100644 --- a/hardware/Makefile.am +++ b/hardware/Makefile.am @@ -33,6 +33,7 @@ SUBDIRS = \ openbench-logic-sniffer \ radioshack-dmm \ tekpower-dmm \ + uni-t-dmm \ zeroplus-logic-cube noinst_LTLIBRARIES = libsigrokhardware.la @@ -94,6 +95,10 @@ if HW_TEKPOWER_DMM libsigrokhardware_la_LIBADD += tekpower-dmm/libsigrokhwtekpowerdmm.la endif +if HW_UNI_T_DMM +libsigrokhardware_la_LIBADD += uni-t-dmm/libsigrok_hw_uni_t_dmm.la +endif + if LA_ZEROPLUS_LOGIC_CUBE libsigrokhardware_la_LIBADD += zeroplus-logic-cube/libsigrokhwzeroplus.la endif diff --git a/hardware/common/Makefile.am b/hardware/common/Makefile.am index e8d670ce..3dd32b0d 100644 --- a/hardware/common/Makefile.am +++ b/hardware/common/Makefile.am @@ -17,6 +17,8 @@ ## along with this program. If not, see . ## +SUBDIRS = dmm + # Local lib, this is NOT meant to be installed! noinst_LTLIBRARIES = libsigrokhwcommon.la @@ -30,6 +32,10 @@ if NEED_SERIAL libsigrokhwcommon_la_SOURCES += serial.c endif +libsigrokhwcommon_la_LIBADD = + +libsigrokhwcommon_la_LIBADD += dmm/libsigrok_hw_common_dmm.la + libsigrokhwcommon_la_CFLAGS = \ -I$(top_srcdir) diff --git a/hardware/common/dmm/Makefile.am b/hardware/common/dmm/Makefile.am new file mode 100644 index 00000000..8f04359a --- /dev/null +++ b/hardware/common/dmm/Makefile.am @@ -0,0 +1,28 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2012 Uwe Hermann +## +## 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 . +## + +# Local lib, this is NOT meant to be installed! +noinst_LTLIBRARIES = libsigrok_hw_common_dmm.la + +libsigrok_hw_common_dmm_la_SOURCES = \ + fs9922.c + +libsigrok_hw_common_dmm_la_CFLAGS = \ + -I$(top_srcdir) + diff --git a/hardware/common/dmm/fs9922.c b/hardware/common/dmm/fs9922.c new file mode 100644 index 00000000..4ff95d9c --- /dev/null +++ b/hardware/common/dmm/fs9922.c @@ -0,0 +1,330 @@ +/* + * This file is part of the sigrok project. + * + * Copyright (C) 2012 Uwe Hermann + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * Fortune Semiconductor FS9922-DMM3/FS9922-DMM4 protocol parser. + */ + +#include +#include +#include +#include +#include "libsigrok.h" +#include "libsigrok-internal.h" + +/* Message logging helpers with driver-specific prefix string. */ +#define DRIVER_LOG_DOMAIN "fs9922: " +#define sr_log(l, s, args...) sr_log(l, DRIVER_LOG_DOMAIN s, ## args) +#define sr_spew(s, args...) sr_spew(DRIVER_LOG_DOMAIN s, ## args) +#define sr_dbg(s, args...) sr_dbg(DRIVER_LOG_DOMAIN s, ## args) +#define sr_info(s, args...) sr_info(DRIVER_LOG_DOMAIN s, ## args) +#define sr_warn(s, args...) sr_warn(DRIVER_LOG_DOMAIN s, ## args) +#define sr_err(s, args...) sr_err(DRIVER_LOG_DOMAIN s, ## args) + +/** + * Parse the numerical value from a protocol packet. + * + * @param buf Buffer containing the 14-byte protocol packet. + * @param result Pointer to a float variable. That variable will contain the + * result value upon parsing success. + * + * @return SR_OK upon success, SR_ERR upon failure. Upon errors, the result + * variable contents are undefined and should not be used. + */ +static int parse_value(const uint8_t *buf, float *result) +{ + int sign, intval; + float floatval; + + /* Byte 0: Sign ('+' or '-') */ + if (buf[0] == '+') { + sign = 1; + } else if (buf[0] == '-') { + sign = -1; + } else { + sr_err("Invalid sign byte: 0x%02x.", buf[0]); + return SR_ERR; + } + + /* + * Bytes 1-4: Value (4 decimal digits) + * + * Over limit: "0.L" on the display, "?0:?" as protocol "digits". + */ + if (buf[1] == '?' && buf[2] == '0' && buf[3] == ':' && buf[4] == '?') { + sr_spew("Over limit."); + *result = INFINITY; + return SR_OK; + } else if (!isdigit(buf[1]) || !isdigit(buf[2]) || + !isdigit(buf[3]) || !isdigit(buf[4])) { + sr_err("Value contained invalid digits: %02x %02x %02x %02x (" + "%c %c %c %c).", buf[1], buf[2], buf[3], buf[4]); + return SR_ERR; + } + intval = 0; + intval += (buf[1] - '0') * 1000; + intval += (buf[2] - '0') * 100; + intval += (buf[3] - '0') * 10; + intval += (buf[4] - '0') * 1; + + floatval = (float)intval; + + /* Byte 5: Always ' ' (space, 0x20) */ + + /* + * Byte 6: Decimal point position ('0', '1', '2', or '4') + * + * Note: The Fortune Semiconductor FS9922-DMM3/4 datasheets both have + * an error/typo here. They claim that the values '0'/'1'/'2'/'3' are + * used, but '0'/'1'/'2'/'4' is actually correct. + */ + if (buf[6] != '0' && buf[6] != '1' && buf[6] != '2' && buf[6] != '4') { + sr_err("Invalid decimal point value: 0x%02x.", buf[6]); + return SR_ERR; + } + if (buf[6] == '0') + floatval /= 1; + else if (buf[6] == '1') + floatval /= 1000; + else if (buf[6] == '2') + floatval /= 100; + else if (buf[6] == '4') + floatval /= 10; + + /* Apply sign. */ + floatval *= sign; + + sr_spew("The display value is %f.", floatval); + + *result = floatval; + + return SR_OK; +} + +/** + * Parse various flags in a protocol packet. + * + * @param buf Buffer containing the 14-byte protocol packet. + * @param floatval Pointer to a float variable which should contain the value + * parsed using parse_value(). That variable will be modified + * in-place depending on the flags in the protocol packet. + * @param analog Pointer to a struct sr_datafeed_analog. The struct will be + * filled with the relevant data according to the flags in the + * protocol packet. + * + * @return SR_OK upon success, SR_ERR upon failure. Upon errors, the 'floatval' + * and 'analog' variable contents are undefined and should not be used. + */ +static int parse_flags(const uint8_t *buf, float *floatval, + struct sr_datafeed_analog *analog) +{ + gboolean is_auto, is_dc, is_ac, is_rel, is_hold, is_bpn, is_z1, is_z2; + gboolean is_max, is_min, is_apo, is_bat, is_nano, is_z3, is_micro; + gboolean is_milli, is_kilo, is_mega, is_beep, is_diode, is_percent; + gboolean is_z4, is_volt, is_ampere, is_ohm, is_hfe, is_hertz, is_farad; + gboolean is_celsius, is_fahrenheit; + int bargraph_sign, bargraph_value; + + /* Z1/Z2/Z3/Z4 are bits for user-defined LCD symbols (on/off). */ + + /* Byte 7 */ + /* Bit 7: Always 0 */ + /* Bit 6: Always 0 */ + is_auto = (buf[7] & (1 << 5)) != 0; + is_dc = (buf[7] & (1 << 4)) != 0; + is_ac = (buf[7] & (1 << 3)) != 0; + is_rel = (buf[7] & (1 << 2)) != 0; + is_hold = (buf[7] & (1 << 1)) != 0; + is_bpn = (buf[7] & (1 << 0)) != 0; /* Bargraph shown */ + + /* Byte 8 */ + is_z1 = (buf[8] & (1 << 7)) != 0; /* User-defined symbol 1 */ + is_z2 = (buf[8] & (1 << 6)) != 0; /* User-defined symbol 2 */ + is_max = (buf[8] & (1 << 5)) != 0; + is_min = (buf[8] & (1 << 4)) != 0; + is_apo = (buf[8] & (1 << 3)) != 0; /* Auto-poweroff active */ + is_bat = (buf[8] & (1 << 2)) != 0; /* Battery low */ + is_nano = (buf[8] & (1 << 1)) != 0; + is_z3 = (buf[8] & (1 << 0)) != 0; /* User-defined symbol 3 */ + + /* Byte 9 */ + is_micro = (buf[9] & (1 << 7)) != 0; + is_milli = (buf[9] & (1 << 6)) != 0; + is_kilo = (buf[9] & (1 << 5)) != 0; + is_mega = (buf[9] & (1 << 4)) != 0; + is_beep = (buf[9] & (1 << 3)) != 0; + is_diode = (buf[9] & (1 << 2)) != 0; + is_percent = (buf[9] & (1 << 1)) != 0; + is_z4 = (buf[8] & (1 << 0)) != 0; /* User-defined symbol 4 */ + + /* Byte 10 */ + is_volt = (buf[10] & (1 << 7)) != 0; + is_ampere = (buf[10] & (1 << 6)) != 0; + is_ohm = (buf[10] & (1 << 5)) != 0; + is_hfe = (buf[10] & (1 << 4)) != 0; + is_hertz = (buf[10] & (1 << 3)) != 0; + is_farad = (buf[10] & (1 << 2)) != 0; + is_celsius = (buf[10] & (1 << 1)) != 0; /* Only FS9922-DMM4 */ + is_fahrenheit = (buf[10] & (1 << 0)) != 0; /* Only FS9922-DMM4 */ + + /* + * Byte 11: Bar graph + * + * Bit 7 contains the sign of the bargraph number (if the bit is set, + * the number is negative), bits 6..0 contain the actual number. + * Valid range: 0-40 (FS9922-DMM3), 0-60 (FS9922-DMM4). + * + * Upon "over limit" the bargraph value is 1 count above the highest + * valid number (i.e. 41 or 61, depending on chip). + */ + if (is_bpn) { + bargraph_sign = ((buf[11] & (1 << 7)) != 0) ? -1 : 1; + bargraph_value = (buf[11] & 0x7f); + bargraph_value *= bargraph_sign; + sr_spew("The bargraph value is %d.", bargraph_value); + } else { + sr_spew("The bargraph is not active."); + } + + /* Byte 12: Always '\r' (carriage return, 0x0d, 13) */ + + /* Byte 13: Always '\n' (newline, 0x0a, 10) */ + + /* Factors */ + if (is_nano) + *floatval /= 1000000000; + if (is_micro) + *floatval /= 1000000; + if (is_milli) + *floatval /= 1000; + if (is_kilo) + *floatval *= 1000; + if (is_mega) + *floatval *= 1000000; + + /* Measurement modes */ + if (is_volt) { + analog->mq = SR_MQ_VOLTAGE; + analog->unit = SR_UNIT_VOLT; + } + if (is_ampere) { + analog->mq = SR_MQ_CURRENT; + analog->unit = SR_UNIT_AMPERE; + } + if (is_ohm) { + analog->mq = SR_MQ_RESISTANCE; + analog->unit = SR_UNIT_OHM; + } + if (is_hfe) { + analog->mq = SR_MQ_GAIN; + analog->unit = SR_UNIT_UNITLESS; + } + if (is_hertz) { + analog->mq = SR_MQ_FREQUENCY; + analog->unit = SR_UNIT_HERTZ; + } + if (is_farad) { + analog->mq = SR_MQ_CAPACITANCE; + analog->unit = SR_UNIT_FARAD; + } + if (is_celsius) { + analog->mq = SR_MQ_TEMPERATURE; + analog->unit = SR_UNIT_CELSIUS; + } + if (is_fahrenheit) { + analog->mq = SR_MQ_TEMPERATURE; + analog->unit = SR_UNIT_FAHRENHEIT; + } + if (is_beep) { + analog->mq = SR_MQ_CONTINUITY; + analog->unit = SR_UNIT_BOOLEAN; + *floatval = (*floatval < 0.0) ? 0.0 : 1.0; + } + if (is_diode) { + analog->mq = SR_MQ_VOLTAGE; + analog->unit = SR_UNIT_VOLT; + } + if (is_percent) { + analog->mq = SR_MQ_DUTY_CYCLE; + analog->unit = SR_UNIT_PERCENTAGE; + } + + /* Measurement related flags */ + if (is_ac) + analog->mqflags |= SR_MQFLAG_AC; + if (is_dc) + analog->mqflags |= SR_MQFLAG_DC; + if (is_auto) + analog->mqflags |= SR_MQFLAG_AUTORANGE; + if (is_hold) + analog->mqflags |= SR_MQFLAG_HOLD; + if (is_max) + analog->mqflags |= SR_MQFLAG_MAX; + if (is_min) + analog->mqflags |= SR_MQFLAG_MIN; + if (is_rel) + analog->mqflags |= SR_MQFLAG_RELATIVE; + + /* Other flags */ + if (is_apo) + sr_spew("Automatic power-off function is active."); + if (is_bat) + sr_spew("Battery is low."); + if (is_z1) + sr_spew("User-defined LCD symbol 1 is active."); + if (is_z2) + sr_spew("User-defined LCD symbol 2 is active."); + if (is_z3) + sr_spew("User-defined LCD symbol 3 is active."); + if (is_z4) + sr_spew("User-defined LCD symbol 4 is active."); + + return SR_OK; +} + +/** + * Parse a Fortune Semiconductor FS9922-DMM3/4 protocol packet. + * + * @param buf Buffer containing the 14-byte protocol packet. + * @param floatval Pointer to a float variable. That variable will be modified + * in-place depending on the protocol packet. + * @param analog Pointer to a struct sr_datafeed_analog. The struct will be + * filled with data according to the protocol packet. + * + * @return SR_OK upon success, SR_ERR upon failure. Upon errors, the + * 'analog' variable contents are undefined and should not be used. + */ +SR_PRIV int sr_dmm_parse_fs9922(const uint8_t *buf, float *floatval, + struct sr_datafeed_analog *analog) +{ + int ret; + + if ((ret = parse_value(buf, floatval)) != SR_OK) { + sr_err("Error parsing value: %d.", ret); + return ret; + } + + if ((ret = parse_flags(buf, floatval, analog)) != SR_OK) { + sr_err("Error parsing flags: %d.", ret); + return ret; + } + + return SR_OK; +} diff --git a/hardware/uni-t-dmm/Makefile.am b/hardware/uni-t-dmm/Makefile.am new file mode 100644 index 00000000..ffbb224a --- /dev/null +++ b/hardware/uni-t-dmm/Makefile.am @@ -0,0 +1,34 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2012 Uwe Hermann +## +## 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 2 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +if HW_UNI_T_DMM + +# Local lib, this is NOT meant to be installed! +noinst_LTLIBRARIES = libsigrok_hw_uni_t_dmm.la + +libsigrok_hw_uni_t_dmm_la_SOURCES = \ + api.c \ + protocol.c \ + protocol.h + +libsigrok_hw_uni_t_dmm_la_CFLAGS = \ + -I$(top_srcdir) + +endif diff --git a/hardware/uni-t-dmm/api.c b/hardware/uni-t-dmm/api.c new file mode 100644 index 00000000..634af3cc --- /dev/null +++ b/hardware/uni-t-dmm/api.c @@ -0,0 +1,372 @@ +/* + * This file is part of the sigrok project. + * + * Copyright (C) 2012 Uwe Hermann + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include "libsigrok.h" +#include "libsigrok-internal.h" +#include "protocol.h" + +static const int hwcaps[] = { + SR_HWCAP_MULTIMETER, + SR_HWCAP_LIMIT_SAMPLES, + SR_HWCAP_LIMIT_MSEC, + SR_HWCAP_CONTINUOUS, + 0, +}; + +static const char *probe_names[] = { + "Probe", + NULL, +}; + +SR_PRIV struct sr_dev_driver uni_t_dmm_driver_info; +static struct sr_dev_driver *di = &uni_t_dmm_driver_info; + +static int open_usb(struct sr_dev_inst *sdi) +{ + libusb_device **devlist; + struct libusb_device_descriptor des; + struct dev_context *devc; + int ret, tmp, cnt, i; + + /* TODO: Use common code later, refactor. */ + + devc = sdi->priv; + + if ((cnt = libusb_get_device_list(NULL, &devlist)) < 0) { + sr_err("Error getting USB device list: %d.", cnt); + return SR_ERR; + } + + ret = SR_ERR; + for (i = 0; i < cnt; i++) { + if ((tmp = libusb_get_device_descriptor(devlist[i], &des))) { + sr_err("Failed to get device descriptor: %d.", tmp); + continue; + } + + if (libusb_get_bus_number(devlist[i]) != devc->usb->bus + || libusb_get_device_address(devlist[i]) != devc->usb->address) + continue; + + if ((tmp = libusb_open(devlist[i], &devc->usb->devhdl))) { + sr_err("Failed to open device: %d.", tmp); + break; + } + + sr_info("Opened USB device on %d.%d.", + devc->usb->bus, devc->usb->address); + ret = SR_OK; + break; + } + libusb_free_device_list(devlist, 1); + + return ret; +} + +static GSList *connect_usb(const char *conn) +{ + struct sr_dev_inst *sdi; + struct drv_context *drvc; + struct dev_context *devc; + struct sr_probe *probe; + libusb_device **devlist; + struct libusb_device_descriptor des; + GSList *devices; + int vid, pid, devcnt, err, i; + + (void)conn; + + /* TODO: Use common code later, refactor. */ + + drvc = di->priv; + + /* Hardcoded for now. */ + vid = UT_D04_CABLE_USB_VID; + pid = UT_D04_CABLE_USB_DID; + + devices = NULL; + libusb_get_device_list(NULL, &devlist); + for (i = 0; devlist[i]; i++) { + if ((err = libusb_get_device_descriptor(devlist[i], &des))) { + sr_err("Failed to get device descriptor: %d", err); + continue; + } + + if (des.idVendor != vid || des.idProduct != pid) + continue; + + if (!(devc = g_try_malloc0(sizeof(struct dev_context)))) { + sr_err("Device context malloc failed."); + return NULL; + } + + devcnt = g_slist_length(drvc->instances); + if (!(sdi = sr_dev_inst_new(devcnt, SR_ST_INACTIVE, + "UNI-T DMM", NULL, NULL))) { + sr_err("sr_dev_inst_new returned NULL."); + return NULL; + } + sdi->priv = devc; + if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "P1"))) + return NULL; + sdi->probes = g_slist_append(sdi->probes, probe); + devc->usb = sr_usb_dev_inst_new( + libusb_get_bus_number(devlist[i]), + libusb_get_device_address(devlist[i]), NULL); + devices = g_slist_append(devices, sdi); + } + libusb_free_device_list(devlist, 1); + + return devices; +} + +static int clear_instances(void) +{ + /* TODO: Use common code later. */ + + return SR_OK; +} + +static int hw_init(void) +{ + int ret; + struct drv_context *drvc; + + if (!(drvc = g_try_malloc0(sizeof(struct drv_context)))) { + sr_err("Driver context malloc failed."); + return SR_ERR_MALLOC; + } + + if ((ret = libusb_init(NULL)) < 0) { + sr_err("Failed to initialize libusb: %s.", + libusb_error_name(ret)); + return SR_ERR; + } + + di->priv = drvc; + + return SR_OK; +} + +static GSList *hw_scan(GSList *options) +{ + GSList *l, *devices; + struct sr_dev_inst *sdi; + struct drv_context *drvc; + + (void)options; + + drvc = di->priv; + + if (!(devices = connect_usb(NULL))) + return NULL; + + for (l = devices; l; l = l->next) { + sdi = l->data; + sdi->driver = di; + drvc->instances = g_slist_append(drvc->instances, l->data); + } + + return devices; +} + +static GSList *hw_dev_list(void) +{ + struct drv_context *drvc; + + drvc = di->priv; + + return drvc->instances; +} + +static int hw_dev_open(struct sr_dev_inst *sdi) +{ + return open_usb(sdi); +} + +static int hw_dev_close(struct sr_dev_inst *sdi) +{ + (void)sdi; + + /* TODO */ + + return SR_OK; +} + +static int hw_cleanup(void) +{ + clear_instances(); + + // libusb_exit(NULL); + + return SR_OK; +} + +static int hw_info_get(int info_id, const void **data, + const struct sr_dev_inst *sdi) +{ + (void)sdi; + + sr_spew("Backend requested info_id %d.", info_id); + + switch (info_id) { + case SR_DI_HWCAPS: + *data = hwcaps; + sr_spew("%s: Returning hwcaps.", __func__); + break; + case SR_DI_NUM_PROBES: + *data = GINT_TO_POINTER(1); + sr_spew("%s: Returning number of probes.", __func__); + break; + case SR_DI_PROBE_NAMES: + *data = probe_names; + sr_spew("%s: Returning probe names.", __func__); + break; + case SR_DI_SAMPLERATES: + /* TODO: Get rid of this. */ + *data = NULL; + sr_spew("%s: Returning samplerates.", __func__); + return SR_ERR_ARG; + break; + case SR_DI_CUR_SAMPLERATE: + /* TODO: Get rid of this. */ + *data = NULL; + sr_spew("%s: Returning current samplerate.", __func__); + return SR_ERR_ARG; + break; + default: + sr_err("%s: Unknown info_id %d.", __func__, info_id); + return SR_ERR_ARG; + break; + } + + return SR_OK; +} + +static int hw_dev_config_set(const struct sr_dev_inst *sdi, int hwcap, + const void *value) +{ + struct dev_context *devc; + + devc = sdi->priv; + + switch (hwcap) { + case SR_HWCAP_LIMIT_MSEC: + /* TODO: Not yet implemented. */ + if (*(const uint64_t *)value == 0) { + sr_err("Time limit cannot be 0."); + return SR_ERR; + } + devc->limit_msec = *(const uint64_t *)value; + sr_dbg("Setting time limit to %" PRIu64 "ms.", + devc->limit_msec); + break; + case SR_HWCAP_LIMIT_SAMPLES: + if (*(const uint64_t *)value == 0) { + sr_err("Sample limit cannot be 0."); + return SR_ERR; + } + devc->limit_samples = *(const uint64_t *)value; + sr_dbg("Setting sample limit to %" PRIu64 ".", + devc->limit_samples); + break; + default: + sr_err("Unknown capability: %d.", hwcap); + return SR_ERR; + break; + } + + return SR_OK; +} + +static int hw_dev_acquisition_start(const struct sr_dev_inst *sdi, + void *cb_data) +{ + struct sr_datafeed_packet packet; + struct sr_datafeed_header header; + struct sr_datafeed_meta_analog meta; + struct dev_context *devc; + + devc = sdi->priv; + + sr_dbg("Starting acquisition."); + + devc->cb_data = cb_data; + + /* Send header packet to the session bus. */ + sr_dbg("Sending SR_DF_HEADER."); + packet.type = SR_DF_HEADER; + packet.payload = (uint8_t *)&header; + header.feed_version = 1; + gettimeofday(&header.starttime, NULL); + sr_session_send(devc->cb_data, &packet); + + /* Send metadata about the SR_DF_ANALOG packets to come. */ + sr_dbg("Sending SR_DF_META_ANALOG."); + packet.type = SR_DF_META_ANALOG; + packet.payload = &meta; + meta.num_probes = 1; + sr_session_send(devc->cb_data, &packet); + + sr_source_add(0, 0, 10 /* poll_timeout */, + uni_t_dmm_receive_data, (void *)sdi); + + return SR_OK; +} + +static int hw_dev_acquisition_stop(const struct sr_dev_inst *sdi, + void *cb_data) +{ + struct sr_datafeed_packet packet; + + (void)sdi; + + sr_dbg("Stopping acquisition."); + + /* Send end packet to the session bus. */ + sr_dbg("Sending SR_DF_END."); + packet.type = SR_DF_END; + sr_session_send(cb_data, &packet); + + /* TODO? */ + sr_source_remove(0); + + return SR_OK; +} + +SR_PRIV struct sr_dev_driver uni_t_dmm_driver_info = { + .name = "uni-t-dmm", + .longname = "UNI-T DMM series", + .api_version = 1, + .init = hw_init, + .cleanup = hw_cleanup, + .scan = hw_scan, + .dev_list = hw_dev_list, + .dev_clear = clear_instances, + .dev_open = hw_dev_open, + .dev_close = hw_dev_close, + .info_get = hw_info_get, + .dev_config_set = hw_dev_config_set, + .dev_acquisition_start = hw_dev_acquisition_start, + .dev_acquisition_stop = hw_dev_acquisition_stop, + .priv = NULL, +}; diff --git a/hardware/uni-t-dmm/protocol.c b/hardware/uni-t-dmm/protocol.c new file mode 100644 index 00000000..086a254a --- /dev/null +++ b/hardware/uni-t-dmm/protocol.c @@ -0,0 +1,256 @@ +/* + * This file is part of the sigrok project. + * + * Copyright (C) 2012 Uwe Hermann + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include "libsigrok.h" +#include "libsigrok-internal.h" +#include "protocol.h" + +/* + * Driver for various UNI-T multimeters (and rebranded ones). + * + * Most UNI-T DMMs can be used with two (three) different PC interface cables: + * - The UT-D04 USB/HID cable, old version with Hoitek HE2325U chip. + * - The UT-D04 USB/HID cable, new version with WCH CH9325 chip. + * - The UT-D01 RS232 cable. + * + * This driver is meant to support all three cables, and various DMMs that + * can be attached to a PC via these cables. Currently only the UT-D04 cable + * (new version) is supported. + * + * The data for one DMM packet (e.g. 14 bytes if the respective DMM uses a + * Fortune Semiconductor FS9922-DMM4 chip) is spread across multiple + * 8-byte chunks. + * + * An 8-byte chunk looks like this: + * - Byte 0: 0xfz, where z is the number of actual data bytes in this chunk. + * - Bytes 1-7: z data bytes, the rest of the bytes should be ignored. + * + * Example: + * f0 00 00 00 00 00 00 00 (no data bytes) + * f2 55 77 00 00 00 00 00 (2 data bytes, 0x55 and 0x77) + * f1 d1 00 00 00 00 00 00 (1 data byte, 0xd1) + * + * Chips and serial settings used in UNI-T DMMs (and rebranded ones): + * - UNI-T UT108: ? + * - UNI-T UT109: ? + * - UNI-T UT30A: ? + * - UNI-T UT30E: ? + * - UNI-T UT60E: Fortune Semiconductor FS9721_LP3 + * - UNI-T UT60G: ? + * - UNI-T UT61B: ? + * - UNI-T UT61C: ? + * - UNI-T UT61D: Fortune Semiconductor FS9922-DMM4 + * - UNI-T UT61E: Cyrustek ES51922 + * - UNI-T UT70B: ? + * - Voltcraft VC-820: Fortune Semiconductor FS9721_LP3 + * - Voltcraft VC-840: Fortune Semiconductor FS9721_LP3 + * - ... + */ + +static void decode_packet(struct dev_context *devc, const uint8_t *buf) +{ + struct sr_datafeed_packet packet; + struct sr_datafeed_analog analog; + float floatval; + int ret; + + memset(&analog, 0, sizeof(struct sr_datafeed_analog)); + + /* Parse the protocol packet. */ + if ((ret = sr_dmm_parse_fs9922(buf, &floatval, &analog)) != SR_OK) { + sr_err("Invalid DMM packet, ignoring."); + return; + } + + /* Send a sample packet with one analog value. */ + analog.num_samples = 1; + analog.data = &floatval; + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + sr_session_send(devc->cb_data, &packet); + + /* Increase sample count. */ + devc->num_samples++; +} + +static int hid_chip_init(struct dev_context *devc, uint16_t baudrate) +{ + int ret; + uint8_t buf[5]; + + /* Detach kernel drivers which grabbed this device (if any). */ + if (libusb_kernel_driver_active(devc->usb->devhdl, 0) == 1) { + ret = libusb_detach_kernel_driver(devc->usb->devhdl, 0); + if (ret < 0) { + sr_err("Failed to detach kernel driver: %d.", ret); + return SR_ERR; + } + sr_dbg("Successfully detached kernel driver."); + } else { + sr_dbg("No need to detach a kernel driver."); + } + + /* Claim interface 0. */ + if ((ret = libusb_claim_interface(devc->usb->devhdl, 0)) < 0) { + sr_err("Failed to claim interface 0: %d.", ret); + return SR_ERR; + } + sr_dbg("Successfully claimed interface 0."); + + /* Baudrate example: 19230 baud -> HEX(19230) == 0x4b1e */ + buf[0] = baudrate & 0xff; /* Baudrate, LSB */ + buf[1] = (baudrate >> 8) & 0xff; /* Baudrate, MSB */ + buf[2] = 0x00; /* Unknown/unused (?) */ + buf[3] = 0x00; /* Unknown/unused (?) */ + buf[4] = 0x03; /* Unknown, always 0x03. */ + + /* Send HID feature report to setup the baudrate/chip. */ + sr_dbg("Sending initial HID feature report."); + sr_spew("HID init = 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x (%d baud)", + buf[0], buf[1], buf[2], buf[3], buf[4], baudrate); + ret = libusb_control_transfer( + devc->usb->devhdl, /* libusb device handle */ + LIBUSB_REQUEST_TYPE_CLASS | + LIBUSB_RECIPIENT_INTERFACE | + LIBUSB_ENDPOINT_OUT, + 9, /* bRequest: HID set_report */ + 0x300, /* wValue: HID feature, report number 0 */ + 0, /* wIndex: interface 0 */ + (unsigned char *)&buf, /* payload buffer */ + 5, /* wLength: 5 bytes payload */ + 1000 /* timeout (ms) */); + + if (ret < 0) { + sr_err("HID feature report error: %d.", ret); + return SR_ERR; + } + + if (ret != 5) { + /* TODO: Handle better by also sending the remaining bytes. */ + sr_err("Short packet: sent %d/5 bytes.", ret); + return SR_ERR; + } + + sr_dbg("Successfully sent initial HID feature report."); + + return SR_OK; +} + +static void log_8byte_chunk(const uint8_t *buf) +{ + sr_spew("8-byte chunk: %02x %02x %02x %02x %02x %02x %02x %02x " + "(%d data bytes)", buf[0], buf[1], buf[2], buf[3], + buf[4], buf[5], buf[6], buf[7], (buf[0] & 0x0f)); +} + +static void log_dmm_packet(const uint8_t *buf) +{ + sr_dbg("DMM packet: %02x %02x %02x %02x %02x %02x %02x" + " %02x %02x %02x %02x %02x %02x %02x", + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], + buf[7], buf[8], buf[9], buf[10], buf[11], buf[12], buf[13]); +} + +SR_PRIV int uni_t_dmm_receive_data(int fd, int revents, void *cb_data) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + int i, ret, len, num_databytes_in_chunk; + uint8_t buf[CHUNK_SIZE]; + uint8_t *pbuf; + static gboolean first_run = TRUE, synced_on_first_packet = FALSE; + static uint64_t data_byte_counter = 0; + + (void)fd; + (void)revents; + + sdi = cb_data; + devc = sdi->priv; + + pbuf = devc->protocol_buf; + + /* On the first run, we need to init the HID chip. */ + if (first_run) { + /* TODO: The baudrate is DMM-specific (UT61D: 19230). */ + if ((ret = hid_chip_init(devc, 19230)) != SR_OK) { + sr_err("HID chip init failed: %d.", ret); + return FALSE; + } + memset(pbuf, 0x00, NUM_DATA_BYTES); + first_run = FALSE; + } + + memset(&buf, 0x00, CHUNK_SIZE); + + /* Get data from EP2 using an interrupt transfer. */ + ret = libusb_interrupt_transfer( + devc->usb->devhdl, /* libusb device handle */ + LIBUSB_ENDPOINT_IN | 2, /* EP2, IN */ + (unsigned char *)&buf, /* receive buffer */ + CHUNK_SIZE, /* wLength */ + &len, /* actually received byte count */ + 1000 /* timeout (ms) */); + + if (ret < 0) { + sr_err("USB receive error: %d.", ret); + return FALSE; + } + + if (len != CHUNK_SIZE) { + sr_err("Short packet: received %d/%d bytes.", len, CHUNK_SIZE); + /* TODO: Print the bytes? */ + return FALSE; + } + + log_8byte_chunk((const uint8_t *)&buf); + + if (buf[0] != 0xf0) { + /* First time: Synchronize to the start of a packet. */ + if (!synced_on_first_packet) { + /* Valid packets start with '+' or '-'. */ + if ((buf[1] != '+') && buf[1] != '-') + return TRUE; + synced_on_first_packet = TRUE; + sr_spew("Successfully synchronized on first packet."); + } + + num_databytes_in_chunk = buf[0] & 0x0f; + for (i = 0; i < num_databytes_in_chunk; i++) + pbuf[data_byte_counter++] = buf[1 + i]; + + /* TODO: Handle > 14 bytes in pbuf? Can this happen? */ + if (data_byte_counter == NUM_DATA_BYTES) { + log_dmm_packet(pbuf); + data_byte_counter = 0; + decode_packet(devc, pbuf); + memset(pbuf, 0x00, NUM_DATA_BYTES); + } + } + + /* Abort acquisition if we acquired enough samples. */ + if (devc->num_samples >= devc->limit_samples && devc->limit_samples > 0) { + sr_info("Requested number of samples reached."); + sdi->driver->dev_acquisition_stop(sdi, cb_data); + } + + return TRUE; +} diff --git a/hardware/uni-t-dmm/protocol.h b/hardware/uni-t-dmm/protocol.h new file mode 100644 index 00000000..2a76c1c1 --- /dev/null +++ b/hardware/uni-t-dmm/protocol.h @@ -0,0 +1,66 @@ +/* + * This file is part of the sigrok project. + * + * Copyright (C) 2012 Uwe Hermann + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBSIGROK_HARDWARE_UNI_T_DMM_PROTOCOL_H +#define LIBSIGROK_HARDWARE_UNI_T_DMM_PROTOCOL_H + +#include +#include +#include +#include "libsigrok.h" +#include "libsigrok-internal.h" + +/* Message logging helpers with driver-specific prefix string. */ +#define DRIVER_LOG_DOMAIN "uni-t-dmm: " +#define sr_log(l, s, args...) sr_log(l, DRIVER_LOG_DOMAIN s, ## args) +#define sr_spew(s, args...) sr_spew(DRIVER_LOG_DOMAIN s, ## args) +#define sr_dbg(s, args...) sr_dbg(DRIVER_LOG_DOMAIN s, ## args) +#define sr_info(s, args...) sr_info(DRIVER_LOG_DOMAIN s, ## args) +#define sr_warn(s, args...) sr_warn(DRIVER_LOG_DOMAIN s, ## args) +#define sr_err(s, args...) sr_err(DRIVER_LOG_DOMAIN s, ## args) + +#define UT_D04_CABLE_USB_VID 0x1a86 +#define UT_D04_CABLE_USB_DID 0xe008 + +#define CHUNK_SIZE 8 +#define NUM_DATA_BYTES 14 + +/** Private, per-device-instance driver context. */ +struct dev_context { + /** The current sampling limit (in number of samples). */ + uint64_t limit_samples; + + /** The current sampling limit (in ms). */ + uint64_t limit_msec; + + /** Opaque pointer passed in by the frontend. */ + void *cb_data; + + /** The current number of already received samples. */ + uint64_t num_samples; + + struct sr_usb_dev_inst *usb; + + uint8_t protocol_buf[14]; +}; + +SR_PRIV int uni_t_dmm_receive_data(int fd, int revents, void *cb_data); + +#endif diff --git a/hwdriver.c b/hwdriver.c index a5bb1ea9..438c34c3 100644 --- a/hwdriver.c +++ b/hwdriver.c @@ -110,6 +110,9 @@ extern SR_PRIV struct sr_dev_driver radioshackdmm_driver_info; #ifdef HAVE_HW_TEKPOWER_DMM extern SR_PRIV struct sr_dev_driver tekpower_dmm_driver_info; #endif +#ifdef HAVE_HW_UNI_T_DMM +extern SR_PRIV struct sr_dev_driver uni_t_dmm_driver_info; +#endif /** @endcond */ static struct sr_dev_driver *drivers_list[] = { @@ -154,6 +157,9 @@ static struct sr_dev_driver *drivers_list[] = { #endif #ifdef HAVE_HW_TEKPOWER_DMM &tekpower_dmm_driver_info, +#endif +#ifdef HAVE_HW_UNI_T_DMM + &uni_t_dmm_driver_info, #endif NULL, }; diff --git a/libsigrok-internal.h b/libsigrok-internal.h index 9983d66a..f39e2398 100644 --- a/libsigrok-internal.h +++ b/libsigrok-internal.h @@ -146,4 +146,9 @@ SR_PRIV int ezusb_upload_firmware(libusb_device *dev, int configuration, const char *filename); #endif +/*--- hardware/common/dmm/fs9922.c ------------------------------------------*/ + +SR_PRIV int sr_dmm_parse_fs9922(const uint8_t *buf, float *floatval, + struct sr_datafeed_analog *analog); + #endif