From: Alexandru Gagniuc Date: Sun, 21 Oct 2012 01:39:37 +0000 (-0500) Subject: Add support for the TekPower TP4000ZC DMM. X-Git-Tag: dsupstream~627 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=7dc55d930f87433fb35ebf6f18f767eddb7e8a17;p=libsigrok.git Add support for the TekPower TP4000ZC DMM. Also known as Digitek DT4000ZC. Signed-off-by: Alexandru Gagniuc --- diff --git a/configure.ac b/configure.ac index 62675bc2..496f96f9 100644 --- a/configure.ac +++ b/configure.ac @@ -188,6 +188,15 @@ if test "x$HW_RADIOSHACK_DMM" = "xyes"; then AC_DEFINE(HAVE_HW_RADIOSHACK_DMM, 1, [RadioShack DMM support]) fi +AC_ARG_ENABLE(tekpower-dmm, AC_HELP_STRING([--enable-tekpower-dmm], + [enable Tekpower DMM support. [default=yes]]), + [HW_TEKPOWER_DMM="$enableval"], + [HW_TEKPOWER_DMM=yes]) +AM_CONDITIONAL(HW_TEKPOWER_DMM, test x$HW_TEKPOWER_DMM = xyes) +if test "x$HW_TEKPOWER_DMM" = "xyes"; then + AC_DEFINE(HAVE_HW_TEKPOWER_DMM, 1, [TekPower DMM support]) +fi + AC_ARG_ENABLE(zeroplus-logic-cube, AC_HELP_STRING([--enable-zeroplus-logic-cube], [enable ZEROPLUS Logic Cube support [default=yes]]), @@ -327,6 +336,7 @@ AC_CONFIG_FILES([Makefile version.h hardware/Makefile hardware/link-mso19/Makefile hardware/openbench-logic-sniffer/Makefile hardware/radioshack-dmm/Makefile + hardware/tekpower-dmm/Makefile hardware/zeroplus-logic-cube/Makefile input/Makefile output/Makefile @@ -370,6 +380,7 @@ echo " - Hantek DSO...................... $HW_HANTEK_DSO" 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 " - ZEROPLUS Logic Cube............. $LA_ZEROPLUS_LOGIC_CUBE" echo diff --git a/hardware/Makefile.am b/hardware/Makefile.am index 0d907ab3..c6e2ae80 100644 --- a/hardware/Makefile.am +++ b/hardware/Makefile.am @@ -32,6 +32,7 @@ SUBDIRS = \ link-mso19 \ openbench-logic-sniffer \ radioshack-dmm \ + tekpower-dmm \ zeroplus-logic-cube noinst_LTLIBRARIES = libsigrokhardware.la @@ -89,6 +90,10 @@ if HW_RADIOSHACK_DMM libsigrokhardware_la_LIBADD += radioshack-dmm/libsigrokhwradioshackdmm.la endif +if HW_TEKPOWER_DMM +libsigrokhardware_la_LIBADD += tekpower-dmm/libsigrokhwtekpowerdmm.la +endif + if LA_ZEROPLUS_LOGIC_CUBE libsigrokhardware_la_LIBADD += zeroplus-logic-cube/libsigrokhwzeroplus.la endif diff --git a/hardware/tekpower-dmm/Makefile.am b/hardware/tekpower-dmm/Makefile.am new file mode 100644 index 00000000..5e7521b1 --- /dev/null +++ b/hardware/tekpower-dmm/Makefile.am @@ -0,0 +1,33 @@ +## +## This file is part of the sigrok project. +## +## 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_TEKPOWER_DMM + +# Local lib, this is NOT meant to be installed! +noinst_LTLIBRARIES = libsigrokhwtekpowerdmm.la + +libsigrokhwtekpowerdmm_la_SOURCES = \ + api.c \ + protocol.c \ + protocol.h + +libsigrokhwtekpowerdmm_la_CFLAGS = \ + -I$(top_srcdir) + +endif diff --git a/hardware/tekpower-dmm/api.c b/hardware/tekpower-dmm/api.c new file mode 100644 index 00000000..1069935c --- /dev/null +++ b/hardware/tekpower-dmm/api.c @@ -0,0 +1,453 @@ +/* + * 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 "protocol.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 tekpower_driver_info; +static struct sr_dev_driver *di = &tekpower_driver_info; + +/* 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("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 *lcd14_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_RDONLY|O_NONBLOCK)) == -1) { + sr_err("unable to open %s: %s", + conn, strerror(errno)); + return NULL; + } + if (serial_set_paramstr(fd, serialcomm) != SR_OK) { + sr_err("unable to set serial parameters"); + return NULL; + } + + sr_info("probing port %s readonly", conn); + + drvc = di->priv; + b = buf; + retry = 0; + devices = NULL; + serial_flush(fd); + /* 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++; + + /* Let's get a bit of data and see if we can find a packet */ + len = sizeof(buf); + serial_readline(fd, &b, &len, 500); + if( (len == 0) || (len < LCD14_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 - LCD14_PACKET_SIZE + 1; + /* don't increment i here */ ) + { + const lcd14_packet *packet = (void *)(&buf[i]); + if( !lcd14_is_packet_valid(packet, NULL) ){ + i++; + continue; + } + good_packets++; + i += LCD14_PACKET_SIZE; + } + + /* If we dropped more than two packets worth of data, something + * is wrong */ + size_t dropped = len - (good_packets * LCD14_PACKET_SIZE); + if(dropped > 2 * LCD14_PACKET_SIZE) + continue; + + /* Let's see if we have anything good */ + if (good_packets == 0) + continue; + + sr_info("found device on port %s", conn); + + if (!(sdi = sr_dev_inst_new(0, SR_ST_INACTIVE, "TekPower", + "TP4000ZC", ""))) + return NULL; + if (!(devc = g_try_malloc0(sizeof(struct dev_context)))) { + sr_dbg("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 = lcd14_scan(conn, serialcomm); + } else { + /* Then try the default 2400 8n1 */ + devices = lcd14_scan(conn, "2400/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("sdi->priv was NULL."); + return SR_ERR_BUG; + } + + devc->serial->fd = serial_open(devc->serial->port, O_RDONLY); + if (devc->serial->fd == -1) { + sr_err("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("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("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("sdi->priv was NULL."); + return SR_ERR_BUG; + } + + switch (hwcap) { + case SR_HWCAP_LIMIT_SAMPLES: + 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; + + if (!(devc = sdi->priv)) { + sr_err("sdi->priv was NULL."); + return SR_ERR_BUG; + } + + sr_dbg("Starting acquisition."); + + devc->cb_data = cb_data; + + /* Reset the number of samples to take. If we've already collected our + * quota, but we start a new session, and don't reset this, we'll just + * quit without aquiring any new samples */ + devc->num_samples = 0; + + /* 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); + + /* Poll every 100ms, or whenever some data comes in. */ + sr_source_add(devc->serial->fd, G_IO_IN, 50, + lcd14_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("sdi->priv was NULL."); + return SR_ERR_BUG; + } + + sr_dbg("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("Sending SR_DF_END."); + packet.type = SR_DF_END; + sr_session_send(cb_data, &packet); + + return SR_OK; +} + +SR_PRIV struct sr_dev_driver tekpower_driver_info = { + .name = "tekpower-dmm", + .longname = "TekPower/Digitek 4000ZC", + .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/tekpower-dmm/protocol.c b/hardware/tekpower-dmm/protocol.c new file mode 100644 index 00000000..a468cb9a --- /dev/null +++ b/hardware/tekpower-dmm/protocol.c @@ -0,0 +1,378 @@ +/* + * This file is part of the sigrok project. + * + * 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 "protocol.h" +#include +#include +#include +#include + + +static gboolean lcd14_is_sync_valid(const lcd14_packet *packet) +{ + size_t i; + /* Check the syncronization nibbles, and make sure they all match */ + for(i = 0; i < LCD14_PACKET_SIZE; i++) + { + uint8_t sync = (packet->raw[i] & LCD14_SYNC_MASK) >> 4; + if(sync != (i+1) ) + return FALSE; + } + return TRUE; +} + +static gboolean lcd14_is_selection_good(const lcd14_data *data) +{ + int n_postfix = 0; + int n_type = 0; + /* Does the packet have more than one multiplier ? */ + if(data->flags & LCD14_NANO) + n_postfix++; + if(data->flags & LCD14_MICRO) + n_postfix++; + if(data->flags & LCD14_MILLI) + n_postfix++; + if(data->flags & LCD14_KILO) + n_postfix++; + if(data->flags & LCD14_MEGA) + n_postfix++; + + if(n_postfix > 1) + return FALSE; + + /* Does the packet "measure" more than one type of value ?*/ + if(data->flags & LCD14_HZ) + n_type++; + if(data->flags & LCD14_OHM) + n_type++; + if(data->flags & LCD14_FARAD) + n_type++; + if(data->flags & LCD14_AMP) + n_type++; + if(data->flags & LCD14_VOLT) + n_type++; + if(data->flags & LCD14_DUTY) + n_type++; + if(data->flags & LCD14_CELSIUS) + n_type++; + /* Do not test for hFE. hFE is not implemented and always '1' */ + if(n_type > 1) + return FALSE; + + /* Both AC and DC ? */ + if( (data->flags & LCD14_AC) && (data->flags & LCD14_DC) ) + return FALSE; + + /* OK, no duplicates */ + return TRUE; +} + +/* We "cook" a raw lcd14_pcaket into a more pallatable form, lcd14_data */ +static void lcd14_cook_raw(const lcd14_packet *packet, lcd14_data * data) +{ + size_t i; + + /* Get the digits out */ + for(i = 0; i < 4; i++) + { + size_t j = (i << 1) + 1; + data->digit[i] = ( (packet->raw[j] & ~LCD14_SYNC_MASK) << 4 ) | + ( (packet->raw[j+1] & ~LCD14_SYNC_MASK) ); + } + + /* Now extract the flags */ + data->flags = ( (packet->raw[0] & ~LCD14_SYNC_MASK) << 20) | + ( (packet->raw[9] & ~LCD14_SYNC_MASK) << 16) | + ( (packet->raw[10]& ~LCD14_SYNC_MASK) << 12) | + ( (packet->raw[11]& ~LCD14_SYNC_MASK) << 8) | + ( (packet->raw[12]& ~LCD14_SYNC_MASK) << 4) | + ( (packet->raw[13]& ~LCD14_SYNC_MASK) ); +} + + +/* Since the DMM 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 mechanism depends + * on how well we can filter out bad packets packets */ +SR_PRIV gboolean lcd14_is_packet_valid(const lcd14_packet *packet, + lcd14_data *data) +{ + /* Callers not interested in the data, pass NULL */ + lcd14_data placeholder; + if(data == NULL) + data = &placeholder; + /* We start with our syncronization nibbles, then move to more advanced + * checks */ + if(!lcd14_is_sync_valid(packet)) + return FALSE; + + lcd14_cook_raw(packet, data); + + if(!lcd14_is_selection_good(data)) + return FALSE; + + /* Made it here, huh? Then this looks to be a valid packet */ + return TRUE; +} + +static uint8_t lcd14_to_digit(uint8_t raw_digit) +{ + /* Take out the decimal point, so we can use a simple switch() */ + raw_digit &= ~LCD14_DP_MASK; + switch(raw_digit) + { + case 0x00: + case LCD14_LCD_0: + return 0; + case LCD14_LCD_1: + return 1; + case LCD14_LCD_2: + return 2; + case LCD14_LCD_3: + return 3; + case LCD14_LCD_4: + return 4; + case LCD14_LCD_5: + return 5; + case LCD14_LCD_6: + return 6; + case LCD14_LCD_7: + return 7; + case LCD14_LCD_8: + return 8; + case LCD14_LCD_9: + return 9; + default: + return LCD14_LCD_INVALID; + } +} + +static double lcdraw_to_double(lcd14_data *data) +{ + /* ********************************************************************* + * Get a raw floating point value from the data + **********************************************************************/ + double rawval; + double multiplier = 1; + uint8_t digit; + gboolean dp_reached = FALSE; + int i; + + /* We have 4 digits, and we start from the most significant */ + for(i = 0; i < 4; i++) + { + uint8_t raw_digit = data->digit[i]; + digit = lcd14_to_digit(raw_digit); + if(digit == LCD14_LCD_INVALID) { + 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 > 0) && (raw_digit & LCD14_DP_MASK) ) + dp_reached = TRUE; + if(dp_reached) multiplier /= 10; + rawval = rawval * 10 + digit; + } + rawval *= multiplier; + if(data->digit[0] & LCD14_D0_NEG) + rawval *= -1; + + /* See if we need to multiply our raw value by anything */ + if(data->flags & LCD14_NANO) { + rawval *= 1E-9; + } else if(data->flags & LCD14_MICRO) { + rawval *= 1E-6; + } else if(data->flags & LCD14_MILLI) { + rawval *= 1E-3; + } else if(data->flags & LCD14_KILO) { + rawval *= 1E3; + } else if(data->flags & LCD14_MEGA) { + rawval *= 1E6; + } + + return rawval; +} + +static void lcd14_handle_packet(lcd14_data *data, struct dev_context *devc) +{ + double rawval = lcdraw_to_double(data); + /* ********************************************************************* + * Now see what the value means, and pass that on + **********************************************************************/ + struct sr_datafeed_packet packet; + struct sr_datafeed_analog *analog; + + if( !(analog = g_try_malloc0(sizeof(struct sr_datafeed_analog))) ) { + sr_err("failed to malloc packet"); + return; + } + analog->num_samples = 1; + if( !(analog->data = g_try_malloc(sizeof(float))) ) { + sr_err("failed to malloc data"); + g_free(analog); + return; + } + *analog->data = (float)rawval; + analog->mq = -1; + + /* What does the data mean ? */ + if(data->flags & LCD14_VOLT) { + analog->mq = SR_MQ_VOLTAGE; + analog->unit = SR_UNIT_VOLT; + if(data->flags & LCD14_AC) + analog->mqflags |= SR_MQFLAG_AC; + else + analog->mqflags |= SR_MQFLAG_DC; + } + else if(data->flags & LCD14_AMP) { + analog->mq = SR_MQ_CURRENT; + analog->unit = SR_UNIT_AMPERE; + if(data->flags & LCD14_AC) + analog->mqflags |= SR_MQFLAG_AC; + else + analog->mqflags |= SR_MQFLAG_DC; + } + else if(data->flags & LCD14_OHM) { + if(data->flags & LCD14_BEEP) + analog->mq = SR_MQ_CONTINUITY; + else + analog->mq = SR_MQ_RESISTANCE; + if(!isnan(rawval) ) + analog->unit = SR_UNIT_OHM; + else { + analog->unit = SR_UNIT_BOOLEAN; + *analog->data = FALSE; + } + } + else if(data->flags & LCD14_FARAD) { + analog->mq = SR_MQ_CAPACITANCE; + analog->unit = SR_UNIT_FARAD; + } + else if(data->flags & LCD14_CELSIUS) { + analog->mq = SR_MQ_TEMPERATURE; + /* No Kelvin or Fahrenheit from the deive, just Celsius */ + analog->unit = SR_UNIT_CELSIUS; + } + else if(data->flags & LCD14_HZ) { + analog->mq = SR_MQ_FREQUENCY; + analog->unit = SR_UNIT_HERTZ; + } + else if(data->flags & LCD14_DUTY) { + analog->mq = SR_MQ_DUTY_CYCLE; + analog->unit = SR_UNIT_PERCENTAGE; + } + else if(data->flags & LCD14_HFE) { + analog->mq = SR_MQ_GAIN; + analog->unit = SR_UNIT_UNITLESS; + } + else if(data->flags & LCD14_DIODE) { + analog->mq = SR_MQ_VOLTAGE; + analog->unit = SR_UNIT_VOLT; + analog->mqflags |= SR_MQFLAG_DIODE | SR_MQFLAG_DC; + } + else { + sr_warn("unable to identify measurement mode"); + } + + /* What other flags are associated with the data? */ + if(data->flags & LCD14_HOLD) { + analog->mqflags |= SR_MQFLAG_HOLD; + } + if(data->flags & LCD14_AUTO) { + analog->mqflags |= SR_MQFLAG_AUTORANGE; + } + if(data->flags & LCD14_REL) { + analog->mqflags |= SR_MQFLAG_RELATIVE; + } + + if (analog->mq != -1) { + /* Got a measurement. */ + sr_spew("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(struct dev_context *devc, int fd) +{ + int len; + size_t i; + size_t offset = 0; + /* Try to get as much data as the buffer can hold */ + len = DMM_BUFSIZE - devc->buflen; + len = serial_read(fd, devc->buf + devc->buflen, len); + if (len < 1) { + sr_err("serial port read error!"); + return; + } + devc->buflen += len; + + /* Now look for packets in that data */ + while((devc->buflen - offset) >= LCD14_PACKET_SIZE) + { + lcd14_packet * packet = (void *)(devc->buf + offset); + lcd14_data data; + if( lcd14_is_packet_valid(packet, &data) ) + { + lcd14_handle_packet(&data, devc); + offset += LCD14_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 lcd14_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/hardware/tekpower-dmm/protocol.h b/hardware/tekpower-dmm/protocol.h new file mode 100644 index 00000000..71dd1d2d --- /dev/null +++ b/hardware/tekpower-dmm/protocol.h @@ -0,0 +1,114 @@ +/* + * This file is part of the sigrok project. + * + * 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_HARDWARE_TEKPOWER_DMM_PROTOCOL_H +#define LIBSIGROK_HARDWARE_TEKPOWER_DMM_PROTOCOL_H + +/* Message logging helpers with driver-specific prefix string. */ +#define DRIVER_LOG_DOMAIN "tekpower-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 DMM_BUFSIZE 256 + +/* Flags present in the packet */ +#define LCD14_AC (1<<23) +#define LCD14_DC (1<<22) +#define LCD14_AUTO (1<<21) +#define LCD14_RS232 (1<<20) +#define LCD14_MICRO (1<<19) +#define LCD14_NANO (1<<18) +#define LCD14_KILO (1<<17) +#define LCD14_DIODE (1<<16) +#define LCD14_MILLI (1<<15) +#define LCD14_DUTY (1<<14) +#define LCD14_MEGA (1<<13) +#define LCD14_BEEP (1<<12) +#define LCD14_FARAD (1<<11) +#define LCD14_OHM (1<<10) +#define LCD14_REL (1<< 9) +#define LCD14_HOLD (1<< 8) +#define LCD14_AMP (1<< 7) +#define LCD14_VOLT (1<< 6) +#define LCD14_HZ (1<< 5) +#define LCD14_LOW_BATT (1<< 4) +#define LCD14_HFE (1<< 3) +#define LCD14_CELSIUS (1<< 2) +#define LCD14_RSVD1 (1<< 1) +#define LCD14_RSVD0 (0<< 0) + +/* mask to remove the decimal point from a digit */ +#define LCD14_DP_MASK (0x80) +#define LCD14_D0_NEG LCD14_DP_MASK +/* mask to remove the syncronization nibble */ +#define LCD14_SYNC_MASK (0xF0) + +/* What the LCD values represent */ +#define LCD14_LCD_0 0x7d +#define LCD14_LCD_1 0x05 +#define LCD14_LCD_2 0x5b +#define LCD14_LCD_3 0x1f +#define LCD14_LCD_4 0x27 +#define LCD14_LCD_5 0x3e +#define LCD14_LCD_6 0x7e +#define LCD14_LCD_7 0x15 +#define LCD14_LCD_8 0x7f +#define LCD14_LCD_9 0x3f + + +#define LCD14_LCD_INVALID 0xff + +typedef struct { + uint8_t raw[14]; +} lcd14_packet; + +typedef struct { + uint8_t digit[4]; + uint32_t flags; +} lcd14_data; + +#define LCD14_PACKET_SIZE (sizeof(lcd14_packet)) + +SR_PRIV gboolean lcd14_is_packet_valid(const lcd14_packet *packet, + lcd14_data *data); + +/* Private, per-device-instance driver context. */ +struct dev_context { + 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[DMM_BUFSIZE]; + size_t bufoffset; + size_t buflen; +}; + + +SR_PRIV int lcd14_receive_data(int fd, int revents, void *cb_data); + +#endif /* LIBSIGROK_HARDWARE_TEKPOWER_DMM_PROTOCOL_H */ diff --git a/hwdriver.c b/hwdriver.c index ef7f3efe..33d6b4a5 100644 --- a/hwdriver.c +++ b/hwdriver.c @@ -107,6 +107,9 @@ extern SR_PRIV struct sr_dev_driver flukedmm_driver_info; #ifdef HAVE_HW_RADIOSHACK_DMM extern SR_PRIV struct sr_dev_driver radioshackdmm_driver_info; #endif +#ifdef HAVE_HW_TEKPOWER_DMM +extern SR_PRIV struct sr_dev_driver tekpower_driver_info; +#endif /** @endcond */ static struct sr_dev_driver *drivers_list[] = { @@ -148,6 +151,9 @@ static struct sr_dev_driver *drivers_list[] = { #endif #ifdef HAVE_HW_RADIOSHACK_DMM &radioshackdmm_driver_info, +#endif +#ifdef HAVE_HW_TEKPOWER_DMM + &tekpower_driver_info, #endif NULL, };