]> sigrok.org Git - libsigrok.git/commitdiff
dmm: introduce support for ASYC-II 16-byte protocol in PRINT mode
authorGerhard Sittig <redacted>
Mon, 28 Nov 2016 00:01:15 +0000 (01:01 +0100)
committerUwe Hermann <redacted>
Tue, 20 Dec 2016 16:51:36 +0000 (17:51 +0100)
Introduce an asycii.c source file (modelled after metex14.c) which
implements support for the 16-byte protocol of the ASYC-II multimeter
chipset (RX only, when the PRINT button was pressed).

Makefile.am
src/dmm/asycii.c [new file with mode: 0644]
src/libsigrok-internal.h

index 7d7d1f9e4707006b1f0f8a60099bdd3bb8f2f1e9..d18a674da3a4e6cffeaca623984361e22dbcf7d0 100644 (file)
@@ -148,6 +148,7 @@ libsigrok_la_SOURCES += \
        src/dmm/fs9922.c \
        src/dmm/m2110.c \
        src/dmm/metex14.c \
+       src/dmm/asycii.c \
        src/dmm/rs9lcd.c \
        src/dmm/bm25x.c \
        src/dmm/ut71x.c \
diff --git a/src/dmm/asycii.c b/src/dmm/asycii.c
new file mode 100644 (file)
index 0000000..8f5770f
--- /dev/null
@@ -0,0 +1,541 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2012-2013 Uwe Hermann <uwe@hermann-uwe.de>
+ * Copyright (C) 2016 Gerhard Sittig <gerhard.sittig@gmx.net>
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Parser for the ASYC-II 16-bytes ASCII protocol (PRINT).
+ *
+ * This should work for various multimeters which use this kind of protocol,
+ * even though there is some variation in which modes each DMM supports.
+ *
+ * This implementation was developed for and tested with a Metrix MX56C,
+ * which is identical to the BK Precision 5390.
+ * See the metex14.c implementation for the 14-byte protocol used by many
+ * other models.
+ */
+
+#include <config.h>
+#include <ctype.h>
+#include <glib.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "asycii"
+
+/**
+ * Parse sign and value from text buffer, byte 0-6.
+ *
+ * The first character always is the sign (' ' or '-'). Subsequent
+ * positions contain digits, dots, or spaces. Overflow / open inputs
+ * are signalled with several magic literals that cannot get interpreted
+ * as a number, either with 'X' characters in them, or with several
+ * forms of "OL".
+ *
+ * @param[in]  buf The text buffer received from the DMM.
+ * @param[out] result A floating point number value.
+ * @param[out] exponent Augments the number value.
+ */
+static int parse_value(const char *buf, struct asycii_info *info,
+                       float *result, int *exponent)
+{
+       char valstr[7 + 1];
+       const char *valp;
+       int i, cnt, is_ol, dot_pos;
+       char *endp;
+
+       /*
+        * Strip all spaces from bytes 0-6. By copying all
+        * non-space characters into a buffer.
+        */
+       cnt = 0;
+       for (i = 0; i < 7; i++) {
+               if (buf[i] != ' ')
+                       valstr[cnt++] = buf[i];
+       }
+       valstr[cnt] = '\0';
+       valp = &valstr[0];
+       sr_spew("%s(), number buffer [%s]", __func__, valp);
+
+       /*
+        * Check for "over limit" conditions. Depending on the meter's
+        * selected mode, the textual representation might differ. Test
+        * all known variations.
+        */
+       is_ol = 0;
+       is_ol += (g_ascii_strcasecmp(valp, ".OL") == 0) ? 1 : 0;
+       is_ol += (g_ascii_strcasecmp(valp, "O.L") == 0) ? 1 : 0;
+       is_ol += (g_ascii_strcasecmp(valp, "-.OL") == 0) ? 1 : 0;
+       is_ol += (g_ascii_strcasecmp(valp, "-O.L") == 0) ? 1 : 0;
+       is_ol += (g_ascii_strncasecmp(valp, "X", 1) == 0) ? 1 : 0;
+       is_ol += (g_ascii_strncasecmp(valp, "-X", 2) == 0) ? 1 : 0;
+       if (is_ol) {
+               sr_spew("%s(), over limit", __func__);
+               *result = INFINITY;
+               return SR_OK;
+       }
+
+       /*
+        * Convert the textual number representation to a float, and
+        * an exponent. Apply sanity checks (optional sign, digits and
+        * dot expected here, exclusively).
+        */
+       endp = NULL;
+       *result = strtof(valp, &endp);
+       if (endp == NULL || *endp != '\0') {
+               info->is_invalid = TRUE;
+               sr_spew("%s(), cannot convert number", __func__);
+               return SR_ERR_DATA;
+       }
+       dot_pos = strcspn(valstr, ".");
+       if (dot_pos < cnt)
+               *exponent = -(cnt - dot_pos - 1);
+       else
+               *exponent = 0;
+       sr_spew("%s(), display value is %f", __func__, *result);
+       return SR_OK;
+}
+
+/**
+ * Parse unit and flags from text buffer, bytes 7-14.
+ *
+ * The unit and flags optionally follow the number value for the
+ * measurement. Either can be present or absent. The scale factor
+ * is always at index 7. The unit starts at index 8, and is of
+ * variable length. Flags immediately follow the unit. The remainder
+ * of the text buffer is SPACE padded, and terminated with CR.
+ *
+ * Notice the implementation detail of case *sensitive* comparison.
+ * This would break correct operation. It's essential that e.g. "Vac"
+ * gets split into the "V" unit and the "ac" flag, not into "VA" and
+ * the unknown "c" flag! In the absence of separators or fixed
+ * positions and with ambiguous text (when abbreviated), order of
+ * comparison matters, too.
+ *
+ * @param[in]  buf The text buffer received from the DMM.
+ * @param[out] info Broken down measurement details.
+ */
+static void parse_flags(const char *buf, struct asycii_info *info)
+{
+       int i, cnt;
+       char unit[8 + 1];
+       const char *u;
+
+       /* Bytes 0-6: Number value, see parse_value(). */
+
+       /* Strip spaces from bytes 7-14. */
+       cnt = 0;
+       for (i = 7; i < 15; i++) {
+               if (buf[i] != ' ')
+                       unit[cnt++] = buf[i];
+       }
+       unit[cnt] = '\0';
+       u = &unit[0];
+       sr_spew("%s(): unit/flag buffer [%s]", __func__, u);
+
+       /* Scan for the scale factor. */
+       sr_spew("%s(): scanning factor, buffer [%s]", __func__, u);
+       if (*u == 'p') {
+               u++;
+               info->is_pico = TRUE;
+       } else if (*u == 'n') {
+               u++;
+               info->is_nano = TRUE;
+       } else if (*u == 'u') {
+               u++;
+               info->is_micro = TRUE;
+       } else if (*u == 'm') {
+               u++;
+               info->is_milli = TRUE;
+       } else if (*u == ' ') {
+               u++;
+       } else if (*u == 'k') {
+               u++;
+               info->is_kilo = TRUE;
+       } else if (*u == 'M') {
+               u++;
+               info->is_mega = TRUE;
+       } else {
+               /* Absence of a scale modifier is perfectly fine. */
+       }
+
+       /* Scan for the measurement unit. */
+       sr_spew("%s(): scanning unit, buffer [%s]", __func__, u);
+       if (strncmp(u, "A", strlen("A")) == 0) {
+               u += strlen("A");
+               info->is_ampere = TRUE;
+       } else if (strncmp(u, "VA", strlen("VA")) == 0) {
+               u += strlen("VA");
+               info->is_volt_ampere = TRUE;
+       } else if (strncmp(u, "V", strlen("V")) == 0) {
+               u += strlen("V");
+               info->is_volt = TRUE;
+       } else if (strncmp(u, "ohm", strlen("ohm")) == 0) {
+               u += strlen("ohm");
+               info->is_resistance = TRUE;
+               info->is_ohm = TRUE;
+       } else if (strncmp(u, "F", strlen("F")) == 0) {
+               u += strlen("F");
+               info->is_capacitance = TRUE;
+               info->is_farad = TRUE;
+       } else if (strncmp(u, "dB", strlen("dB")) == 0) {
+               u += strlen("dB");
+               info->is_gain = TRUE;
+               info->is_decibel = TRUE;
+       } else if (strncmp(u, "Hz", strlen("Hz")) == 0) {
+               u += strlen("Hz");
+               info->is_frequency = TRUE;
+               info->is_hertz = TRUE;
+       } else if (strncmp(u, "%", strlen("%")) == 0) {
+               u += strlen("%");
+               info->is_duty_cycle = TRUE;
+               if (*u == '+') {
+                       u++;
+                       info->is_duty_pos = TRUE;
+               } else if (*u == '-') {
+                       u++;
+                       info->is_duty_neg = TRUE;
+               } else {
+                       info->is_invalid = TRUE;
+               }
+       } else if (strncmp(u, "Cnt", strlen("Cnt")) == 0) {
+               u += strlen("Cnt");
+               info->is_pulse_count = TRUE;
+               info->is_unitless = TRUE;
+               if (*u == '+') {
+                       u++;
+                       info->is_count_pos = TRUE;
+               } else if (*u == '-') {
+                       u++;
+                       info->is_count_neg = TRUE;
+               } else {
+                       info->is_invalid = TRUE;
+               }
+       } else if (strncmp(u, "s", strlen("s")) == 0) {
+               u += strlen("s");
+               info->is_pulse_width = TRUE;
+               info->is_seconds = TRUE;
+               if (*u == '+') {
+                       u++;
+                       info->is_period_pos = TRUE;
+               } else if (*u == '-') {
+                       u++;
+                       info->is_period_neg = TRUE;
+               } else {
+                       info->is_invalid = TRUE;
+               }
+       } else {
+               /* Not strictly illegal, but unknown/unsupported. */
+               sr_spew("%s(): measurement: unsupported", __func__);
+               info->is_invalid = TRUE;
+       }
+
+       /* Scan for additional flags. */
+       sr_spew("%s(): scanning flags, buffer [%s]", __func__, u);
+       if (strncmp(u, "ac+dc", strlen("ac+dc")) == 0) {
+               u += strlen("ac+dc");
+               info->is_ac_and_dc = TRUE;
+       } else if (strncmp(u, "ac", strlen("ac")) == 0) {
+               u += strlen("ac");
+               info->is_ac = TRUE;
+       } else if (strncmp(u, "dc", strlen("dc")) == 0) {
+               u += strlen("dc");
+               info->is_dc = TRUE;
+       } else if (strncmp(u, "d", strlen("d")) == 0) {
+               u += strlen("d");
+               info->is_diode = TRUE;
+       } else if (strncmp(u, "Pk", strlen("Pk")) == 0) {
+               u += strlen("Pk");
+               if (*u == '+') {
+                       u++;
+                       info->is_peak_max = TRUE;
+               } else if (*u == '-') {
+                       u++;
+                       info->is_peak_min = TRUE;
+               } else {
+                       info->is_invalid = TRUE;
+               }
+       } else if (strcmp(u, "") == 0) {
+               /* Absence of any flags is acceptable. */
+       } else {
+               /* Presence of unknown flags is not. */
+               sr_dbg("%s(): flag: unknown", __func__);
+               info->is_invalid = TRUE;
+       }
+
+       /* Was all of the received data consumed? */
+       if (*u != '\0')
+               info->is_invalid = TRUE;
+
+       /*
+        * Note:
+        * - The protocol does not distinguish between "resistance"
+        *   and "continuity".
+        * - Relative measurement and hold cannot get recognized.
+        */
+}
+
+/**
+ * Fill in a datafeed from previously parsed measurement details.
+ *
+ * @param[out] analog The datafeed which gets filled in.
+ * @param[in]  floatval The number value of the measurement.
+ * @param[in]  exponent Augments the number value.
+ * @param[in]  info Scale and unit and other attributes.
+ */
+static void handle_flags(struct sr_datafeed_analog *analog, float *floatval,
+                        int *exponent, const struct asycii_info *info)
+{
+       int factor = 0;
+
+       /* Factors */
+       if (info->is_pico)
+               factor -= 12;
+       if (info->is_nano)
+               factor -= 9;
+       if (info->is_micro)
+               factor -= 6;
+       if (info->is_milli)
+               factor -= 3;
+       if (info->is_kilo)
+               factor += 3;
+       if (info->is_mega)
+               factor += 6;
+       *floatval *= powf(10, factor);
+       *exponent += factor;
+
+       /* Measurement modes */
+       if (info->is_volt) {
+               analog->meaning->mq = SR_MQ_VOLTAGE;
+               analog->meaning->unit = SR_UNIT_VOLT;
+       }
+       if (info->is_volt_ampere) {
+               analog->meaning->mq = SR_MQ_POWER;
+               analog->meaning->unit = SR_UNIT_VOLT_AMPERE;
+       }
+       if (info->is_ampere) {
+               analog->meaning->mq = SR_MQ_CURRENT;
+               analog->meaning->unit = SR_UNIT_AMPERE;
+       }
+       if (info->is_frequency) {
+               analog->meaning->mq = SR_MQ_FREQUENCY;
+               analog->meaning->unit = SR_UNIT_HERTZ;
+       }
+       if (info->is_duty_cycle) {
+               analog->meaning->mq = SR_MQ_DUTY_CYCLE;
+               analog->meaning->unit = SR_UNIT_PERCENTAGE;
+       }
+       if (info->is_pulse_width) {
+               analog->meaning->mq = SR_MQ_PULSE_WIDTH;
+               analog->meaning->unit = SR_UNIT_SECOND;
+       }
+       if (info->is_pulse_count) {
+               analog->meaning->mq = SR_MQ_COUNT;
+               analog->meaning->unit = SR_UNIT_UNITLESS;
+       }
+       if (info->is_resistance) {
+               analog->meaning->mq = SR_MQ_RESISTANCE;
+               analog->meaning->unit = SR_UNIT_OHM;
+       }
+       if (info->is_capacitance) {
+               analog->meaning->mq = SR_MQ_CAPACITANCE;
+               analog->meaning->unit = SR_UNIT_FARAD;
+       }
+       if (info->is_diode) {
+               analog->meaning->mq = SR_MQ_VOLTAGE;
+               analog->meaning->unit = SR_UNIT_VOLT;
+       }
+       if (info->is_gain) {
+               analog->meaning->mq = SR_MQ_GAIN;
+               analog->meaning->unit = SR_UNIT_DECIBEL_VOLT;
+       }
+
+       /* Measurement related flags */
+       if (info->is_ac)
+               analog->meaning->mqflags |= SR_MQFLAG_AC;
+       if (info->is_ac_and_dc)
+               analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_DC;
+       if (info->is_dc)
+               analog->meaning->mqflags |= SR_MQFLAG_DC;
+       if (info->is_diode)
+               analog->meaning->mqflags |= SR_MQFLAG_DIODE;
+       if (info->is_peak_max)
+               analog->meaning->mqflags |= SR_MQFLAG_MAX;
+       if (info->is_peak_min)
+               analog->meaning->mqflags |= SR_MQFLAG_MIN;
+}
+
+/**
+ * Check measurement details for consistency and validity.
+ *
+ * @param[in]  info The previously parsed details.
+ *
+ * @return     TRUE on success, FALSE otherwise.
+ */
+static gboolean flags_valid(const struct asycii_info *info)
+{
+       int count;
+
+       /* Have previous checks raised the "invalid" flag? */
+       if (info->is_invalid) {
+               sr_dbg("Previous parse raised \"invalid\" flag for packet.");
+               return FALSE;
+       }
+
+       /* Does the packet have more than one multiplier? */
+       count = 0;
+       count += (info->is_pico) ? 1 : 0;
+       count += (info->is_nano) ? 1 : 0;
+       count += (info->is_micro) ? 1 : 0;
+       count += (info->is_milli) ? 1 : 0;
+       count += (info->is_kilo) ? 1 : 0;
+       count += (info->is_mega) ? 1 : 0;
+       if (count > 1) {
+               sr_dbg("More than one multiplier detected in packet.");
+               return FALSE;
+       }
+
+       /* Does the packet "measure" more than one type of value? */
+       count = 0;
+       count += (info->is_volt || info->is_diode) ? 1 : 0;
+       count += (info->is_volt_ampere) ? 1 : 0;
+       count += (info->is_ampere) ? 1 : 0;
+       count += (info->is_gain) ? 1 : 0;
+       count += (info->is_resistance) ? 1 : 0;
+       count += (info->is_capacitance) ? 1 : 0;
+       count += (info->is_frequency) ? 1 : 0;
+       count += (info->is_duty_cycle) ? 1 : 0;
+       count += (info->is_pulse_width) ? 1 : 0;
+       count += (info->is_pulse_count) ? 1 : 0;
+       if (count > 1) {
+               sr_dbg("More than one measurement type detected in packet.");
+               return FALSE;
+       }
+
+       /* Are conflicting AC and DC flags set? */
+       count = 0;
+       count += (info->is_ac) ? 1 : 0;
+       count += (info->is_ac_and_dc) ? 1 : 0;
+       count += (info->is_dc) ? 1 : 0;
+       if (count > 1) {
+               sr_dbg("Conflicting AC and DC flags detected in packet.");
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+#ifdef HAVE_LIBSERIALPORT
+/**
+ * Arrange for the reception of another measurement from the DMM.
+ *
+ * This routine is unused in the currently implemented PRINT mode,
+ * where the meter sends measurements to the PC in pre-set intervals,
+ * without the PC's intervention.
+ *
+ * @param[in]  serial The serial connection.
+ *
+ * @private
+ */
+SR_PRIV int sr_asycii_packet_request(struct sr_serial_dev_inst *serial)
+{
+       /*
+        * The current implementation assumes that the user pressed
+        * the PRINT button. It has no support to query/trigger packet
+        * reception from the meter.
+        */
+       (void)serial;
+       sr_spew("NOT requesting DMM packet.");
+       return SR_OK;
+}
+#endif
+
+/**
+ * Check whether a received frame is valid.
+ *
+ * @param[in]  buf The text buffer with received data.
+ *
+ * @return     TRUE upon success, FALSE otherwise.
+ */
+SR_PRIV gboolean sr_asycii_packet_valid(const uint8_t *buf)
+{
+       struct asycii_info info;
+
+       /* First check whether we are in sync with the packet stream. */
+       if (buf[15] != '\r')
+               return FALSE;
+
+       /* Have the received packet content parsed. */
+       memset(&info, 0x00, sizeof(info));
+       parse_flags((const char *)buf, &info);
+       if (!flags_valid(&info))
+               return FALSE;
+
+       return TRUE;
+}
+
+/**
+ * Parse a protocol packet.
+ *
+ * @param[in]  buf Buffer containing the protocol packet. Must not be NULL.
+ * @param[out] floatval Pointer to a float variable. That variable will
+ *             be modified in-place depending on the protocol packet.
+ *             Must not be NULL.
+ * @param[out] analog Pointer to a struct sr_datafeed_analog. The struct
+ *             will be filled with data according to the protocol packet.
+ *             Must not be NULL.
+ * @param[out] info Pointer to a struct asycii_info. The struct will be
+ *             filled with data according to the protocol packet. Must
+ *             not be NULL.
+ *
+ * @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_asycii_parse(const uint8_t *buf, float *floatval,
+                           struct sr_datafeed_analog *analog, void *info)
+{
+       int ret, exponent;
+       struct asycii_info *info_local;
+
+       info_local = (struct asycii_info *)info;
+
+       /* Don't print byte 15. That one contains the carriage return. */
+       sr_dbg("DMM packet: \"%.15s\"", buf);
+
+       memset(info_local, 0x00, sizeof(*info_local));
+
+       exponent = 0;
+       ret = parse_value((const char *)buf, info_local, floatval, &exponent);
+       if (ret != SR_OK) {
+               sr_dbg("Error parsing value: %d.", ret);
+               return ret;
+       }
+
+       parse_flags((const char *)buf, info_local);
+       handle_flags(analog, floatval, &exponent, info_local);
+
+       analog->encoding->digits = -exponent;
+       analog->spec->spec_digits = -exponent;
+
+       return SR_OK;
+}
index 0856c48013ad95eae0ecc3f6e9b1894a0d26a8c0..fa4432fb8946b3e7957a4de26fc39b778563b295 100644 (file)
@@ -1321,6 +1321,31 @@ SR_PRIV gboolean sr_ut372_packet_valid(const uint8_t *buf);
 SR_PRIV int sr_ut372_parse(const uint8_t *buf, float *floatval,
                struct sr_datafeed_analog *analog, void *info);
 
+/*--- hardware/dmm/asycii.c -------------------------------------------------*/
+
+#define ASYCII_PACKET_SIZE 16
+
+struct asycii_info {
+       gboolean is_ac, is_dc, is_ac_and_dc;
+       gboolean is_resistance, is_capacitance, is_diode, is_gain;
+       gboolean is_frequency, is_duty_cycle, is_duty_pos, is_duty_neg;
+       gboolean is_pulse_width, is_period_pos, is_period_neg;
+       gboolean is_pulse_count, is_count_pos, is_count_neg;
+       gboolean is_ampere, is_volt, is_volt_ampere, is_farad, is_ohm;
+       gboolean is_hertz, is_percent, is_seconds, is_decibel;
+       gboolean is_pico, is_nano, is_micro, is_milli, is_kilo, is_mega;
+       gboolean is_unitless;
+       gboolean is_peak_min, is_peak_max;
+       gboolean is_invalid;
+};
+
+#ifdef HAVE_LIBSERIALPORT
+SR_PRIV int sr_asycii_packet_request(struct sr_serial_dev_inst *serial);
+#endif
+SR_PRIV gboolean sr_asycii_packet_valid(const uint8_t *buf);
+SR_PRIV int sr_asycii_parse(const uint8_t *buf, float *floatval,
+                           struct sr_datafeed_analog *analog, void *info);
+
 /*--- hardware/scale/kern.c -------------------------------------------------*/
 
 struct kern_info {