diff --git a/Makefile.am b/Makefile.am index e2ccabb6..beeb0c35 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,6 +2,7 @@ ## This file is part of the sigrok project. ## ## Copyright (C) 2010-2012 Bert Vermeulen +## Copyright (C) 2012 Alexandru Gagniuc ## ## 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 diff --git a/configure.ac b/configure.ac index 7b47ec8e..bc51a1e7 100644 --- a/configure.ac +++ b/configure.ac @@ -2,6 +2,7 @@ ## This file is part of the sigrok project. ## ## Copyright (C) 2010-2012 Bert Vermeulen +## Copyright (C) 2012 Alexandru Gagniuc ## ## 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 @@ -122,6 +123,15 @@ if test "x$HW_FLUKE_DMM" = "xyes"; then AC_DEFINE(HAVE_HW_FLUKE_DMM, 1, [Fluke DMM support]) fi +AC_ARG_ENABLE(radioshack-dmm, AC_HELP_STRING([--enable-radioshack-dmm], + [Enable Fluke DMM support. [default=yes]]), + [HW_RADIOSHACK_DMM="$enableval"], + [HW_RADIOSHACK_DMM=yes]) +AM_CONDITIONAL(HW_RADIOSHACK_DMM, test x$HW_RADIOSHACK_DMM = xyes) +if test "x$HW_RADIOSHACK_DMM" = "xyes"; then + AC_DEFINE(HAVE_HW_RADIOSHACK_DMM, 1, [Radioshack DMM support]) + fi + AC_ARG_ENABLE(fx2lafw, AC_HELP_STRING([--enable-fx2lafw], [enable fx2lafw support (for FX2 LAs). [default=yes]]), [LA_FX2LAFW="$enableval"], @@ -313,6 +323,7 @@ AC_CONFIG_FILES([Makefile hardware/common/Makefile hardware/demo/Makefile hardware/fluke-dmm/Makefile + hardware/radioshack-dmm/Makefile hardware/fx2lafw/Makefile hardware/genericdmm/Makefile hardware/link-mso19/Makefile @@ -357,6 +368,7 @@ echo " - ASIX SIGMA/SIGMA2............... $LA_ASIX_SIGMA" echo " - ChronoVu LA8.................... $LA_CHRONOVU_LA8" echo " - Demo driver..................... $LA_DEMO" echo " - Fluke DMM....................... $HW_FLUKE_DMM" +echo " - Radioshack DMM.................. $HW_RADIOSHACK_DMM" echo " - fx2lafw (for FX2 LAs)........... $LA_FX2LAFW" echo " - Generic DMM..................... $HW_GENERICDMM" echo " - Link MSO-19..................... $LA_LINK_MSO19" diff --git a/hardware/Makefile.am b/hardware/Makefile.am index db5691d5..029a222e 100644 --- a/hardware/Makefile.am +++ b/hardware/Makefile.am @@ -2,6 +2,7 @@ ## This file is part of the sigrok project. ## ## Copyright (C) 2011 Uwe Hermann +## Copyright (C) 2012 Alexandru Gagniuc ## ## 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 @@ -30,7 +31,8 @@ SUBDIRS = \ link-mso19 \ openbench-logic-sniffer \ zeroplus-logic-cube \ - hantek-dso + hantek-dso \ + radioshack-dmm noinst_LTLIBRARIES = libsigrokhardware.la @@ -63,6 +65,10 @@ if HW_FLUKE_DMM libsigrokhardware_la_LIBADD += fluke-dmm/libsigrokhwflukedmm.la endif +if HW_RADIOSHACK_DMM +libsigrokhardware_la_LIBADD += radioshack-dmm/libsigrokhwradioshackdmm.la +endif + if LA_FX2LAFW libsigrokhardware_la_LIBADD += fx2lafw/libsigrokhwfx2lafw.la endif diff --git a/hardware/radioshack-dmm/Makefile.am b/hardware/radioshack-dmm/Makefile.am new file mode 100644 index 00000000..99091aa1 --- /dev/null +++ b/hardware/radioshack-dmm/Makefile.am @@ -0,0 +1,34 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2012 Bert Vermeulen +## Copyright (C) 2012 Alexandru Gagniuc +## +## 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 . +## + +if HW_RADIOSHACK_DMM + +# Local lib, this is NOT meant to be installed! +noinst_LTLIBRARIES = libsigrokhwradioshackdmm.la + +libsigrokhwradioshackdmm_la_SOURCES = \ + api.c \ + radioshack.c \ + radioshack-dmm.h + +libsigrokhwradioshackdmm_la_CFLAGS = \ + -I$(top_srcdir) + +endif diff --git a/hardware/radioshack-dmm/api.c b/hardware/radioshack-dmm/api.c new file mode 100644 index 00000000..ecb4ad5c --- /dev/null +++ b/hardware/radioshack-dmm/api.c @@ -0,0 +1,451 @@ +/* + * This file is part of the sigrok project. + * + * Copyright (C) 2012 Bert Vermeulen + * Copyright (C) 2012 Alexandru Gagniuc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "libsigrok/libsigrok.h" +#include "libsigrok-internal.h" +#include "config.h" +#include "radioshack-dmm.h" +#include +#include +#include +#include +#include + +static const int hwopts[] = { + SR_HWOPT_CONN, + SR_HWOPT_SERIALCOMM, + 0, +}; + +static const int hwcaps[] = { + SR_HWCAP_MULTIMETER, + SR_HWCAP_LIMIT_SAMPLES, + SR_HWCAP_CONTINUOUS, + 0, +}; + +static const char *probe_names[] = { + "Probe", + NULL, +}; + +SR_PRIV struct sr_dev_driver radioshackdmm_driver_info; +static struct sr_dev_driver *di = &radioshackdmm_driver_info; + +static const struct radioshackdmm_profile supported_radioshackdmm[] = { + { RADIOSHACK_22_812, "22-812", 100 }, +}; + + +/* Properly close and free all devices. */ +static int clear_instances(void) +{ + struct sr_dev_inst *sdi; + struct drv_context *drvc; + struct dev_context *devc; + GSList *l; + + if (!(drvc = di->priv)) + return SR_OK; + + drvc = di->priv; + for (l = drvc->instances; l; l = l->next) { + if (!(sdi = l->data)) + continue; + if (!(devc = sdi->priv)) + continue; + sr_serial_dev_inst_free(devc->serial); + sr_dev_inst_free(sdi); + } + g_slist_free(drvc->instances); + drvc->instances = NULL; + + return SR_OK; +} + +static int hw_init(void) +{ + struct drv_context *drvc; + + if (!(drvc = g_try_malloc0(sizeof(struct drv_context)))) { + sr_err("radioshack-dmm: driver context malloc failed."); + return SR_ERR; + } + + di->priv = drvc; + + return SR_OK; +} + +static int serial_readline(int fd, char **buf, size_t *buflen, + uint64_t timeout_ms) +{ + uint64_t start; + int maxlen, len; + + timeout_ms *= 1000; + start = g_get_monotonic_time(); + + maxlen = *buflen; + *buflen = len = 0; + while(1) { + len = maxlen - *buflen - 1; + if (len < 1) + break; + len = serial_read(fd, *buf + *buflen, 1); + if (len > 0) { + *buflen += len; + *(*buf + *buflen) = '\0'; + if (*buflen > 0 && *(*buf + *buflen - 1) == '\r') { + /* Strip LF and terminate. */ + *(*buf + --*buflen) = '\0'; + break; + } + } + if (g_get_monotonic_time() - start > timeout_ms) + /* Timeout */ + break; + g_usleep(2000); + } + + return SR_OK; +} + +static GSList *rs_22_812_scan(const char *conn, const char *serialcomm) +{ + struct sr_dev_inst *sdi; + struct drv_context *drvc; + struct dev_context *devc; + struct sr_probe *probe; + GSList *devices; + int fd, retry; + size_t len; + char buf[128], *b; + + if ((fd = serial_open(conn, O_RDWR|O_NONBLOCK)) == -1) { + sr_err("radioshack-dmm: unable to open %s: %s", + conn, strerror(errno)); + return NULL; + } + if (serial_set_paramstr(fd, serialcomm) != SR_OK) { + sr_err("radioshack-dmm: unable to set serial parameters"); + return NULL; + } + + drvc = di->priv; + b = buf; + retry = 0; + devices = NULL; + /* There's no way to get an ID from the multimeter. It just sends data + * periodically, so the best we can do is check if the packets match the + * expected format. */ + while (!devices && retry < 3) + { + size_t i; + size_t good_packets = 0; + retry++; + serial_flush(fd); + + /* Let's get a bit of data and see if we can find a packet */ + len = sizeof(buf); + serial_readline(fd, &b, &len, 250); + if( (len == 0) || (len < RS_22_812_PACKET_SIZE) ) { + /* Not enough data received, is the DMM connected ? */ + continue; + } + + /* Let's treat our buffer like a stream, and find any + * valid packets */ + for( i = 0; i < len - RS_22_812_PACKET_SIZE + 1; + /* don't increment i here */ ) + { + const rs_22_812_packet *packet = (void *)(&buf[i]); + if( !rs_22_812_is_packet_valid(packet) ){ + i++; + continue; + } + good_packets++; + i += RS_22_812_PACKET_SIZE; + } + + /* If we dropped more than two packets worth of data, something + * is wrong */ + size_t dropped = len - (good_packets * RS_22_812_PACKET_SIZE); + if(dropped > 2 * RS_22_812_PACKET_SIZE) + continue; + + /* Let's see if we have anything good */ + if (good_packets == 0) + continue; + + if (!(sdi = sr_dev_inst_new(0, SR_ST_INACTIVE, "Radioshack", + "22-812", ""))) + return NULL; + if (!(devc = g_try_malloc0(sizeof(struct dev_context)))) { + sr_dbg("radioshack-dmm: failed to malloc devc"); + return NULL; + } + + /* devc->profile = RADIOSHACK_22_812; */ + devc->serial = sr_serial_dev_inst_new(conn, -1); + devc->serialcomm = g_strdup(serialcomm); + + sdi->priv = devc; + sdi->driver = di; + if (!(probe = sr_probe_new(0, SR_PROBE_ANALOG, TRUE, "P1"))) + return NULL; + sdi->probes = g_slist_append(sdi->probes, probe); + drvc->instances = g_slist_append(drvc->instances, sdi); + devices = g_slist_append(devices, sdi); + break; + + } + serial_close(fd); + + return devices; +} + +static GSList *hw_scan(GSList *options) +{ + struct sr_hwopt *opt; + GSList *l, *devices; + const char *conn, *serialcomm; + + conn = serialcomm = NULL; + for (l = options; l; l = l->next) { + opt = l->data; + switch (opt->hwopt) { + case SR_HWOPT_CONN: + conn = opt->value; + break; + case SR_HWOPT_SERIALCOMM: + serialcomm = opt->value; + break; + } + } + if (!conn) + return NULL; + + if (serialcomm) { + /* Use the provided comm specs. */ + devices = rs_22_812_scan(conn, serialcomm); + } else { + /* Then try the default 4800 8n1 */ + devices = rs_22_812_scan(conn, "4800/8n1"); + } + + 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) +{ + struct dev_context *devc; + + if (!(devc = sdi->priv)) { + sr_err("radioshack-dmm: sdi->priv was NULL."); + return SR_ERR_BUG; + } + + devc->serial->fd = serial_open(devc->serial->port, O_RDWR | O_NONBLOCK); + if (devc->serial->fd == -1) { + sr_err("radioshack-dmm: Couldn't open serial port '%s'.", + devc->serial->port); + return SR_ERR; + } + if (serial_set_paramstr(devc->serial->fd, devc->serialcomm) != SR_OK) { + sr_err("radioshack-dmm: unable to set serial parameters"); + return SR_ERR; + } + sdi->status = SR_ST_ACTIVE; + + return SR_OK; +} + +static int hw_dev_close(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + + if (!(devc = sdi->priv)) { + sr_err("radioshack-dmm: sdi->priv was NULL."); + return SR_ERR_BUG; + } + + if (devc->serial && devc->serial->fd != -1) { + serial_close(devc->serial->fd); + devc->serial->fd = -1; + sdi->status = SR_ST_INACTIVE; + } + + return SR_OK; +} + +static int hw_cleanup(void) +{ + clear_instances(); + + return SR_OK; +} + +static int hw_info_get(int info_id, const void **data, + const struct sr_dev_inst *sdi) +{ + (void)sdi; /* Does nothing. prevents "unused parameter" warning */ + + switch (info_id) { + case SR_DI_HWOPTS: + *data = hwopts; + break; + case SR_DI_HWCAPS: + *data = hwcaps; + break; + case SR_DI_NUM_PROBES: + *data = GINT_TO_POINTER(1); + break; + case SR_DI_PROBE_NAMES: + *data = probe_names; + break; + default: + return SR_ERR_ARG; + } + + return SR_OK; +} + +static int hw_dev_config_set(const struct sr_dev_inst *sdi, int hwcap, + const void *value) +{ + struct dev_context *devc; + + if (sdi->status != SR_ST_ACTIVE) + return SR_ERR; + + if (!(devc = sdi->priv)) { + sr_err("radioshack-dmm: sdi->priv was NULL."); + return SR_ERR_BUG; + } + + switch (hwcap) { + case SR_HWCAP_LIMIT_SAMPLES: + devc->limit_samples = *(const uint64_t *)value; + sr_dbg("radioshack-dmm: Setting sample limit to %" PRIu64 ".", + devc->limit_samples); + break; + default: + sr_err("radioshack-dmm: 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; + + if (!(devc = sdi->priv)) { + sr_err("radioshack-dmm: sdi->priv was NULL."); + return SR_ERR_BUG; + } + + sr_dbg("radioshack-dmm: Starting acquisition."); + + devc->cb_data = cb_data; + + /* Send header packet to the session bus. */ + sr_dbg("radioshack-dmm: 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("radioshack-dmm: 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); + + /* Poll every 100ms, or whenever some data comes in. */ + sr_source_add(devc->serial->fd, G_IO_IN, 50, + radioshack_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; + struct dev_context *devc; + + if (sdi->status != SR_ST_ACTIVE) + return SR_ERR; + + if (!(devc = sdi->priv)) { + sr_err("radioshack-dmm: sdi->priv was NULL."); + return SR_ERR_BUG; + } + + sr_dbg("radioshack-dmm: Stopping acquisition."); + + sr_source_remove(devc->serial->fd); + hw_dev_close((struct sr_dev_inst *)sdi); + + /* Send end packet to the session bus. */ + sr_dbg("radioshack-dmm: Sending SR_DF_END."); + packet.type = SR_DF_END; + sr_session_send(cb_data, &packet); + + return SR_OK; +} + +SR_PRIV struct sr_dev_driver radioshackdmm_driver_info = { + .name = "radioshack-dmm", + .longname = "Radioshack 22-812/22-039 DMMs", + .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/radioshack-dmm/radioshack-dmm.c b/hardware/radioshack-dmm/radioshack-dmm.c new file mode 100644 index 00000000..e69de29b diff --git a/hardware/radioshack-dmm/radioshack-dmm.h b/hardware/radioshack-dmm/radioshack-dmm.h new file mode 100644 index 00000000..d206d0fb --- /dev/null +++ b/hardware/radioshack-dmm/radioshack-dmm.h @@ -0,0 +1,159 @@ +/* + * This file is part of the sigrok project. + * + * Copyright (C) 2012 Bert Vermeulen + * Copyright (C) 2012 Alexandru Gagniuc + * + * 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 . + */ + +#ifndef LIBSIGROK_RADIOSHACK_DMM_H +#define LIBSIGROK_RADIOSHACK_DMM_H + +#define RS_DMM_BUFSIZE 256 + +/* Byte 1 of the packet, and the modes it represents */ +#define RS_22_812_IND1_HZ (0x80) +#define RS_22_812_IND1_OHM (0x40) +#define RS_22_812_IND1_KILO (0x20) +#define RS_22_812_IND1_MEGA (0x10) +#define RS_22_812_IND1_FARAD (0x08) +#define RS_22_812_IND1_AMP (0x04) +#define RS_22_812_IND1_VOLT (0x02) +#define RS_22_812_IND1_MILI (0x01) +/* Byte 2 of the packet, and the modes it represents */ +#define RS_22_812_IND2_MICRO (0x80) +#define RS_22_812_IND2_NANO (0x40) +#define RS_22_812_IND2_DBM (0x20) +#define RS_22_812_IND2_SEC (0x10) +#define RS_22_812_IND2_DUTY (0x08) +#define RS_22_812_IND2_HFE (0x04) +#define RS_22_812_IND2_REL (0x02) +#define RS_22_812_IND2_MIN (0x01) +/* Byte 7 of the packet, and the modes it represents */ +#define RS_22_812_INFO_BEEP (0x80) +#define RS_22_812_INFO_DIODE (0x30) +#define RS_22_812_INFO_BAT (0x20) +#define RS_22_812_INFO_HOLD (0x10) +#define RS_22_812_INFO_NEG (0x08) +#define RS_22_812_INFO_AC (0x04) +#define RS_22_812_INFO_RS232 (0x02) +#define RS_22_812_INFO_AUTO (0x01) +/* Instead of a decimal point, digit 4 carries the MAX flag */ +#define RS_22_812_DIG4_MAX (0x08) +/* mask to remove the decimal point fr0m a digit */ +#define RS_22_812_DP_MASK (0x08) + +/* What the LCD values represent */ +#define RS_22_812_LCD_0 0xd7 +#define RS_22_812_LCD_1 0x50 +#define RS_22_812_LCD_2 0xb5 +#define RS_22_812_LCD_3 0xf1 +#define RS_22_812_LCD_4 0x72 +#define RS_22_812_LCD_5 0xe3 +#define RS_22_812_LCD_6 0xe7 +#define RS_22_812_LCD_7 0x51 +#define RS_22_812_LCD_8 0xf7 +#define RS_22_812_LCD_9 0xf3 + +#define RS_22_812_LCD_C 0x87 +#define RS_22_812_LCD_E +#define RS_22_812_LCD_F +#define RS_22_812_LCD_h 0x66 +#define RS_22_812_LCD_H +#define RS_22_812_LCD_I +#define RS_22_812_LCD_n +#define RS_22_812_LCD_P 0x37 +#define RS_22_812_LCD_r + +typedef struct { + uint8_t mode; + uint8_t indicatrix1; + uint8_t indicatrix2; + uint8_t digit4; + uint8_t digit3; + uint8_t digit2; + uint8_t digit1; + uint8_t info; + uint8_t checksum; +} rs_22_812_packet; + +#define RS_22_812_PACKET_SIZE (sizeof(rs_22_812_packet)) + +typedef enum { + RS_22_812_MODE_DC_V = 0, + RS_22_812_MODE_AC_V = 1, + RS_22_812_MODE_DC_UA = 2, + RS_22_812_MODE_DC_MA = 3, + RS_22_812_MODE_DC_A = 4, + RS_22_812_MODE_AC_UA = 5, + RS_22_812_MODE_AC_MA = 6, + RS_22_812_MODE_AC_A = 7, + RS_22_812_MODE_OHM = 8, + RS_22_812_MODE_FARAD = 9, + RS_22_812_MODE_HZ = 10, + RS_22_812_MODE_VOLT_HZ = 11, + RS_22_812_MODE_AMP_HZ = 12, + RS_22_812_MODE_DUTY = 13, + RS_22_812_MODE_VOLT_DUTY= 14, + RS_22_812_MODE_AMP_DUTY = 15, + RS_22_812_MODE_WIDTH = 16, + RS_22_812_MODE_VOLT_WIDTH = 17, + RS_22_812_MODE_AMP_WIDTH = 18, + RS_22_812_MODE_DIODE = 19, + RS_22_812_MODE_CONT = 20, + RS_22_812_MODE_HFE = 21, + RS_22_812_MODE_LOGIC = 22, + RS_22_812_MODE_DBM = 23, + //RS_22_812_MODE_ EF = 24, + RS_22_812_MODE_TEMP = 25, + RS_22_812_MODE_INVALID = 26, +} rs_22_812_mode; + +SR_PRIV gboolean rs_22_812_is_packet_valid(const rs_22_812_packet *data ); + +/* Supported models */ +typedef enum { + RADIOSHACK_22_812 = 1, +} radioshack_model; + +/* Supported device profiles */ +struct radioshackdmm_profile { + radioshack_model model; + const char *modelname; + /* How often to poll, in ms. */ + int poll_period; +}; + +/* Private, per-device-instance driver context. */ +typedef struct dev_context { + /* const struct radioshackdmm_profile *profile; */ + uint64_t limit_samples; + struct sr_serial_dev_inst *serial; + char *serialcomm; + + /* Opaque pointer passed in by the frontend. */ + void *cb_data; + + /* Runtime. */ + uint64_t num_samples; + uint8_t buf[RS_DMM_BUFSIZE]; + size_t bufoffset; + size_t buflen; +} rs_dev_ctx; + + +SR_PRIV int radioshack_receive_data(int fd, int revents, void *cb_data); + +#endif /* LIBSIGROK_RADIOSHACK_DMM_H */ diff --git a/hardware/radioshack-dmm/radioshack.c b/hardware/radioshack-dmm/radioshack.c new file mode 100644 index 00000000..078160e0 --- /dev/null +++ b/hardware/radioshack-dmm/radioshack.c @@ -0,0 +1,406 @@ +/* + * This file is part of the sigrok project. + * + * Copyright (C) 2012 Bert Vermeulen + * Copyright (C) 2012 Alexandru Gagniuc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "libsigrok.h" +#include "libsigrok-internal.h" +#include "config.h" +#include "radioshack-dmm.h" +#include +#include +#include +#include + + +static gboolean rs_22_812_is_checksum_valid(const rs_22_812_packet *data) +{ + uint8_t *raw = (void *) data; + uint8_t sum = 0; + size_t i; + for(i = 0; i < RS_22_812_PACKET_SIZE - 1; i++) + sum += raw[i]; + /* This is just a funky constant added to the checksum */ + sum += 57; + sum -= data->checksum; + return(sum == 0); +} + +static gboolean rs_22_812_is_mode_valid(rs_22_812_mode mode) +{ + return(mode < RS_22_812_MODE_INVALID); +} + +static gboolean rs_22_812_is_selection_good(const rs_22_812_packet *data) +{ + int n_postfix = 0; + int n_type = 0; + /* Does the packet have more than one multiplier ? */ + if(data->indicatrix1 & RS_22_812_IND1_KILO) + n_postfix++; + if(data->indicatrix1 & RS_22_812_IND1_MEGA) + n_postfix++; + if(data->indicatrix1 & RS_22_812_IND1_MILI) + n_postfix++; + if(data->indicatrix2 & RS_22_812_IND2_MICRO) + n_postfix++; + if(data->indicatrix2 & RS_22_812_IND2_NANO) + n_postfix++; + if(n_postfix > 1) + return FALSE; + + /* Does the packet "measure" more than one type of value ?*/ + if(data->indicatrix1 & RS_22_812_IND1_HZ) + n_type++; + if(data->indicatrix1 & RS_22_812_IND1_OHM) + n_type++; + if(data->indicatrix1 & RS_22_812_IND1_FARAD) + n_type++; + if(data->indicatrix1 & RS_22_812_IND1_AMP) + n_type++; + if(data->indicatrix1 & RS_22_812_IND1_VOLT) + n_type++; + if(data->indicatrix2 & RS_22_812_IND2_DBM) + n_type++; + if(data->indicatrix2 & RS_22_812_IND2_SEC) + n_type++; + if(data->indicatrix2 & RS_22_812_IND2_DUTY) + n_type++; + if(data->indicatrix2 & RS_22_812_IND2_HFE) + n_type++; + if(n_type > 1) + return FALSE; + + /* OK, no duplicates */ + return TRUE; +} + +/* Since the RS 22-812 does not identify itslef in any way shape, or form, + * we really don't know for sure who is sending the data. We must use every + * possible check to filter out bad packets, especially since detection of the + * 22-812 depends on how well we can filter the packets */ +SR_PRIV gboolean rs_22_812_is_packet_valid(const rs_22_812_packet *packet) +{ + /* Unfortunately, the packet doesn't have a signature, so we must + * compute its checksum first */ + if(!rs_22_812_is_checksum_valid(packet)) + return FALSE; + + if(!rs_22_812_is_mode_valid(packet->mode)) + return FALSE; + + if(!rs_22_812_is_selection_good(packet)) { + return FALSE; + } + /* Made it here, huh? Then this looks to be a valid packet */ + return TRUE; +} + +static uint8_t rs_22_812_to_digit(uint8_t raw_digit) +{ + /* Take out the decimal point, so we can use a simple switch() */ + raw_digit &= ~RS_22_812_DP_MASK; + switch(raw_digit) + { + case 0x00: + case RS_22_812_LCD_0: + return 0; + case RS_22_812_LCD_1: + return 1; + case RS_22_812_LCD_2: + return 2; + case RS_22_812_LCD_3: + return 3; + case RS_22_812_LCD_4: + return 4; + case RS_22_812_LCD_5: + return 5; + case RS_22_812_LCD_6: + return 6; + case RS_22_812_LCD_7: + return 7; + case RS_22_812_LCD_8: + return 8; + case RS_22_812_LCD_9: + return 9; + default: + return 0xff; + } +} + +typedef enum { + READ_ALL, + READ_TEMP, +} value_type; + +static double lcdraw_to_double(rs_22_812_packet *rs_packet, value_type type) +{ + /* ********************************************************************* + * Get a raw floating point value from the data + **********************************************************************/ + double rawval; + double multiplier = 1; + uint8_t digit; + gboolean dp_reached = FALSE; + int i, end; + switch(type) { + case READ_TEMP: + /* Do not parse the last digit */ + end = 1; + break; + case READ_ALL: + default: + /* Parse all digits */ + end = 0; + } + /* We have 4 digits, and we start from the most significant */ + for(i = 3; i >= end; i--) + { + uint8_t raw_digit = *(&(rs_packet->digit4) + i); + digit = rs_22_812_to_digit(raw_digit); + if(digit == 0xff) { + rawval = NAN; + break; + } + /* Digit 1 does not have a decimal point. Instead, the decimal + * point is used to indicate MAX, so we must avoid testing it */ + if( (i < 3) && (raw_digit & RS_22_812_DP_MASK) ) + dp_reached = TRUE; + if(dp_reached) multiplier /= 10; + rawval = rawval * 10 + digit; + } + rawval *= multiplier; + if(rs_packet->info & RS_22_812_INFO_NEG) + rawval *= -1; + + /* See if we need to multiply our raw value by anything */ + if(rs_packet->indicatrix1 & RS_22_812_IND2_NANO) { + rawval *= 1E-9; + } else if(rs_packet->indicatrix2 & RS_22_812_IND2_MICRO) { + rawval *= 1E-6; + } else if(rs_packet->indicatrix1 & RS_22_812_IND1_MILI) { + rawval *= 1E-3; + } else if(rs_packet->indicatrix1 & RS_22_812_IND1_KILO) { + rawval *= 1E3; + } else if(rs_packet->indicatrix1 & RS_22_812_IND1_MEGA) { + rawval *= 1E6; + } + + return rawval; +} + +static gboolean rs_22_812_is_celsius(rs_22_812_packet *rs_packet) +{ + return((rs_packet->digit4 & ~RS_22_812_DP_MASK) == RS_22_812_LCD_C); +} + +static gboolean rs_22_812_is_shortcirc(rs_22_812_packet *rs_packet) +{ + return((rs_packet->digit2 & ~RS_22_812_DP_MASK) == RS_22_812_LCD_h); +} + +static void rs_22_812_handle_packet(rs_22_812_packet *rs_packet, + rs_dev_ctx *devc) +{ + double rawval = lcdraw_to_double(rs_packet, READ_ALL); + /* ********************************************************************* + * Now see what the value means, and pass that on + **********************************************************************/ + struct sr_datafeed_packet packet; + struct sr_datafeed_analog *analog; + + analog = g_try_malloc0(sizeof(struct sr_datafeed_analog)); + analog->num_samples = 1; + analog->data = g_try_malloc(sizeof(float)); + *analog->data = (float)rawval; + analog->mq = -1; + + switch(rs_packet->mode) { + case RS_22_812_MODE_DC_V: + analog->mq = SR_MQ_VOLTAGE; + analog->unit = SR_UNIT_VOLT; + analog->mqflags |= SR_MQFLAG_DC; + break; + case RS_22_812_MODE_AC_V: + analog->mq = SR_MQ_VOLTAGE; + analog->unit = SR_UNIT_VOLT; + analog->mqflags |= SR_MQFLAG_AC; + break; + case RS_22_812_MODE_DC_UA: + case RS_22_812_MODE_DC_MA: + case RS_22_812_MODE_DC_A: + analog->mq = SR_MQ_CURRENT; + analog->unit = SR_UNIT_AMPERE; + analog->mqflags |= SR_MQFLAG_DC; + break; + case RS_22_812_MODE_AC_UA: + case RS_22_812_MODE_AC_MA: + case RS_22_812_MODE_AC_A: + analog->mq = SR_MQ_CURRENT; + analog->unit = SR_UNIT_AMPERE; + analog->mqflags |= SR_MQFLAG_AC; + break; + case RS_22_812_MODE_OHM: + analog->mq = SR_MQ_RESISTANCE; + analog->unit = SR_UNIT_OHM; + break; + case RS_22_812_MODE_FARAD: + analog->mq = SR_MQ_CAPACITANCE; + analog->unit = SR_UNIT_FARAD; + break; + case RS_22_812_MODE_CONT: + analog->mq = SR_MQ_CONTINUITY; + analog->unit = SR_UNIT_BOOLEAN; + *analog->data = rs_22_812_is_shortcirc(rs_packet); + break; + case RS_22_812_MODE_DIODE: + analog->mq = SR_MQ_VOLTAGE; + analog->unit = SR_UNIT_VOLT; + analog->mqflags |= SR_MQFLAG_DIODE | SR_MQFLAG_DC; + break; + case RS_22_812_MODE_HZ: + case RS_22_812_MODE_VOLT_HZ: + case RS_22_812_MODE_AMP_HZ: + analog->mq = SR_MQ_FREQUENCY; + analog->unit = SR_UNIT_HERTZ; + break; + case RS_22_812_MODE_LOGIC: + analog->mq = 0; /* FIXME */ + analog->unit = SR_UNIT_BOOLEAN; + sr_warn("radioshack-dmm: LOGIC mode not supported yet"); + g_free(analog->data); + g_free(analog); + return; + break; + case RS_22_812_MODE_HFE: + analog->mq = SR_MQ_GAIN; + analog->unit = SR_UNIT_UNITLESS; + break; + case RS_22_812_MODE_DUTY: + case RS_22_812_MODE_VOLT_DUTY: + case RS_22_812_MODE_AMP_DUTY: + analog->mq = SR_MQ_DUTY_CYCLE; + analog->unit = SR_UNIT_PERCENTAGE; + break; + case RS_22_812_MODE_WIDTH: + case RS_22_812_MODE_VOLT_WIDTH: + case RS_22_812_MODE_AMP_WIDTH: + analog->mq = SR_MQ_PULSE_WIDTH; + analog->unit = SR_UNIT_SECOND; + case RS_22_812_MODE_TEMP: + analog->mq = SR_MQ_TEMPERATURE; + /* We need to reparse */ + *analog->data = lcdraw_to_double(rs_packet, READ_TEMP); + analog->unit = rs_22_812_is_celsius(rs_packet)? + SR_UNIT_CELSIUS:SR_UNIT_FAHRENHEIT; + break; + case RS_22_812_MODE_DBM: + analog->mq = SR_MQ_POWER; + analog->unit = SR_UNIT_DECIBEL_MW; + analog->mqflags |= SR_MQFLAG_AC; + break; + default: + sr_warn("radioshack-dmm: unkown mode: %d", rs_packet->mode); + break; + } + + if(rs_packet->info & RS_22_812_INFO_HOLD) { + analog->mqflags |= SR_MQFLAG_HOLD; + } + if(rs_packet->digit4 & RS_22_812_DIG4_MAX) { + analog->mqflags |= SR_MQFLAG_MAX; + } + if(rs_packet->indicatrix2 & RS_22_812_IND2_MIN) { + analog->mqflags |= SR_MQFLAG_MIN; + } + if(rs_packet->info & RS_22_812_INFO_AUTO) { + analog->mqflags |= SR_MQFLAG_AUTORANGE; + } + + if (analog->mq != -1) { + /* Got a measurement. */ + sr_spew("radioshack-dmm: val %f", rawval); + packet.type = SR_DF_ANALOG; + packet.payload = analog; + sr_session_send(devc->cb_data, &packet); + devc->num_samples++; + } + g_free(analog->data); + g_free(analog); +} + +static void handle_new_data(rs_dev_ctx *devc, int fd) +{ + int len; + size_t i; + size_t offset = 0; + /* Try to get as much data as the buffer can hold */ + len = RS_DMM_BUFSIZE - devc->buflen; + len = serial_read(fd, devc->buf + devc->buflen, len); + if (len < 1) { + sr_err("radioshack-dmm: serial port read error!"); + return; + } + devc->buflen += len; + + /* Now look for packets in that data */ + while((devc->buflen - offset) >= RS_22_812_PACKET_SIZE) + { + rs_22_812_packet * packet = (void *)(devc->buf + offset); + if( rs_22_812_is_packet_valid(packet) ) + { + rs_22_812_handle_packet(packet, devc); + offset += RS_22_812_PACKET_SIZE; + } else { + offset++; + } + } + + /* If we have any data left, move it to the beginning of our buffer */ + for(i = 0; i < devc->buflen - offset; i++) + devc->buf[i] = devc->buf[offset + i]; + devc->buflen -= offset; +} + +SR_PRIV int radioshack_receive_data(int fd, int revents, void *cb_data) +{ + const struct sr_dev_inst *sdi; + struct dev_context *devc; + + if (!(sdi = cb_data)) + return TRUE; + + if (!(devc = sdi->priv)) + return TRUE; + + if (revents == G_IO_IN) + { + /* Serial data arrived. */ + handle_new_data(devc, fd); + } + + if (devc->num_samples >= devc->limit_samples) { + sdi->driver->dev_acquisition_stop(sdi, cb_data); + return TRUE; + } + + return TRUE; +} + + diff --git a/hwdriver.c b/hwdriver.c index 083504c5..3b1718d3 100644 --- a/hwdriver.c +++ b/hwdriver.c @@ -89,6 +89,9 @@ extern SR_PRIV struct sr_dev_driver agdmm_driver_info; #ifdef HAVE_HW_FLUKE_DMM extern SR_PRIV struct sr_dev_driver flukedmm_driver_info; #endif +#ifdef HAVE_HW_RADIOSHACK_DMM +extern SR_PRIV struct sr_dev_driver radioshackdmm_driver_info; +#endif static struct sr_dev_driver *drivers_list[] = { #ifdef HAVE_LA_DEMO @@ -126,6 +129,9 @@ static struct sr_dev_driver *drivers_list[] = { #endif #ifdef HAVE_HW_FLUKE_DMM &flukedmm_driver_info, +#endif +#ifdef HAVE_HW_RADIOSHACK_DMM + &radioshackdmm_driver_info, #endif NULL, };