]> sigrok.org Git - libsigrok.git/commitdiff
Implement Brymen BM25x series as a serial DMM.
authorJanne Huttunen <redacted>
Wed, 30 Jul 2014 07:28:58 +0000 (10:28 +0300)
committerUwe Hermann <redacted>
Fri, 1 Aug 2014 18:31:26 +0000 (20:31 +0200)
The Brymen BM25x series supports the BC-20X that is an opto-isolated
serial cable. The link seems to be unidirectional i.e. when activated
the DMM periodically sends updates to the host while the host cannot
control the DMM in any way.

The protocol is documented in "6000-count-digital-multimeters-r1.pdf"
that is available from the manufacturer. Every 15 byte packet consists
of a bitmap where the bits correspond to segments or symbols on the
LCD display i.e. the DMM essentially sends the contents of its screen
to the host in every update. This driver then decodes the measured
quantity, unit and its value from the bitmap.

Makefile.am
src/dmm/bm25x.c [new file with mode: 0644]
src/drivers.c
src/hardware/serial-dmm/api.c
src/hardware/serial-dmm/protocol.c
src/hardware/serial-dmm/protocol.h
src/libsigrok-internal.h

index 8ffc99a94745a60c519b5f60d29c322b89339b78..d961ad5694648ac5bbfef37368f73eeb5d106780 100644 (file)
@@ -99,7 +99,8 @@ libsigrok_la_SOURCES += \
        src/dmm/fs9922.c \
        src/dmm/m2110.c \
        src/dmm/metex14.c \
-       src/dmm/rs9lcd.c
+       src/dmm/rs9lcd.c \
+       src/dmm/bm25x.c
 
 # Hardware drivers
 if HW_AGILENT_DMM
diff --git a/src/dmm/bm25x.c b/src/dmm/bm25x.c
new file mode 100644 (file)
index 0000000..67c2d04
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2014 Janne Huttunen <jahuttun@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file
+ *
+ * Brymen BM25x serial protocol parser.
+ */
+
+#include <math.h>
+#include "libsigrok.h"
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "brymen-bm25x"
+
+#define MAX_DIGITS 4
+
+SR_PRIV gboolean sr_brymen_bm25x_packet_valid(const uint8_t *buf)
+{
+       int i;
+
+       if (buf[0] != 2)
+               return FALSE;
+
+       for (i = 1; i < BRYMEN_BM25X_PACKET_SIZE; i++)
+               if ((buf[i] >> 4) != i)
+                       return FALSE;
+
+       return TRUE;
+}
+
+static int decode_digit(int num, const uint8_t *buf)
+{
+       int val;
+
+       val = (buf[3 + 2 * num] & 0xe) | ((buf[4 + 2 * num] << 4) & 0xf0);
+
+       switch (val) {
+       case 0xbe: return 0;
+       case 0xa0: return 1;
+       case 0xda: return 2;
+       case 0xf8: return 3;
+       case 0xe4: return 4;
+       case 0x7c: return 5;
+       case 0x7e: return 6;
+       case 0xa8: return 7;
+       case 0xfe: return 8;
+       case 0xfc: return 9;
+       case 0x00: return ' ';
+       case 0x40: return '-';
+       case 0x16: return 'L';
+       case 0x1e: return 'C';
+       case 0x4e: return 'F';
+       case 0x5e: return 'E';
+       case 0x62: return 'n';
+       case 0x42: return 'r';
+       default:
+               sr_dbg("Unknown digit: 0x%02x.", val);
+               return -1;
+       }
+}
+
+static int decode_point(const uint8_t *buf)
+{
+       int i, p = 0;
+
+       for (i = 1; i < MAX_DIGITS; i++) {
+               if ((buf[11 - 2 * i] & 1) == 0)
+                       continue;
+               if (p != 0) {
+                       sr_spew("Multiple decimal points found!");
+                       return -1;
+               }
+               p = i;
+       }
+
+       return p;
+}
+
+static float scale_value(float val, int point, int digits)
+{
+       int pos;
+
+       pos = point ? point + digits - MAX_DIGITS : 0;
+
+       switch (pos) {
+       case 0: return val;
+       case 1: return val * 1e-1;
+       case 2: return val * 1e-2;
+       case 3: return val * 1e-3;
+       }
+
+       sr_dbg("Invalid decimal point %d (%d digits).", point, digits);
+
+       return NAN;
+}
+
+static float decode_prefix(const uint8_t *buf)
+{
+       if (buf[11] & 2) return 1e+6;
+       if (buf[11] & 1) return 1e+3;
+       if (buf[13] & 1) return 1e-3;
+       if (buf[13] & 2) return 1e-6;
+       if (buf[12] & 1) return 1e-9;
+
+       return 1.0f;
+}
+
+static float decode_value(const uint8_t *buf)
+{
+       float val = 0.0f;
+       int i, digit;
+
+       for (i = 0; i < MAX_DIGITS; i++) {
+               digit = decode_digit(i, buf);
+               if (i == 3 && (digit == 'C' || digit == 'F'))
+                       break;
+               if (digit < 0 || digit > 9)
+                       goto special;
+               val = 10.0 * val + digit;
+       }
+
+       return scale_value(val, decode_point(buf), i);
+
+special:
+       if (decode_digit(1, buf) == 0 && decode_digit(2, buf) == 'L')
+               return INFINITY;
+
+       return NAN;
+}
+
+SR_PRIV int sr_brymen_bm25x_parse(const uint8_t *buf, float *floatval,
+                               struct sr_datafeed_analog *analog, void *info)
+{
+       float val;
+
+       (void)info;
+
+       analog->mq = SR_MQ_GAIN;
+       analog->unit = SR_UNIT_UNITLESS;
+       analog->mqflags = 0;
+
+       if (buf[1] & 8)
+               analog->mqflags |= SR_MQFLAG_AUTORANGE;
+       if (buf[1] & 4)
+               analog->mqflags |= SR_MQFLAG_DC;
+       if (buf[1] & 2)
+               analog->mqflags |= SR_MQFLAG_AC;
+       if (buf[1] & 1)
+               analog->mqflags |= SR_MQFLAG_RELATIVE;
+       if (buf[11] & 8)
+               analog->mqflags |= SR_MQFLAG_HOLD;
+       if (buf[13] & 8)
+               analog->mqflags |= SR_MQFLAG_MAX;
+       if (buf[14] & 8)
+               analog->mqflags |= SR_MQFLAG_MIN;
+
+       if (buf[14] & 4) {
+               analog->mq = SR_MQ_VOLTAGE;
+               analog->unit = SR_UNIT_VOLT;
+               if ((analog->mqflags & (SR_MQFLAG_DC | SR_MQFLAG_AC)) == 0)
+                       analog->mqflags |= SR_MQFLAG_DIODE;
+       }
+       if (buf[14] & 2) {
+               analog->mq = SR_MQ_CURRENT;
+               analog->unit = SR_UNIT_AMPERE;
+       }
+       if (buf[12] & 4) {
+               analog->mq = SR_MQ_RESISTANCE;
+               analog->unit = SR_UNIT_OHM;
+       }
+       if (buf[13] & 4) {
+               analog->mq = SR_MQ_CAPACITANCE;
+               analog->unit = SR_UNIT_FARAD;
+       }
+       if (buf[12] & 2) {
+               analog->mq = SR_MQ_FREQUENCY;
+               analog->unit = SR_UNIT_HERTZ;
+       }
+
+       if (decode_digit(3, buf) == 'C') {
+               analog->mq = SR_MQ_TEMPERATURE;
+               analog->unit = SR_UNIT_CELSIUS;
+       }
+       if (decode_digit(3, buf) == 'F') {
+               analog->mq = SR_MQ_TEMPERATURE;
+               analog->unit = SR_UNIT_FAHRENHEIT;
+       }
+
+       val = decode_value(buf) * decode_prefix(buf);
+
+       if (buf[3] & 1)
+               val = -val;
+
+       *floatval = val;
+
+       return SR_OK;
+}
index f93214d31b5c7ea4d3b2d2d9bce27a6e541a9a0b..8aa9acf51869b4ec7d044b31a7405902c6901ed0 100644 (file)
@@ -149,6 +149,7 @@ extern SR_PRIV struct sr_dev_driver uni_t_ut61e_ser_driver_info;
 extern SR_PRIV struct sr_dev_driver iso_tech_idm103n_driver_info;
 extern SR_PRIV struct sr_dev_driver tenma_72_7745_ser_driver_info;
 extern SR_PRIV struct sr_dev_driver tenma_72_7750_ser_driver_info;
+extern SR_PRIV struct sr_dev_driver brymen_bm25x_ser_driver_info;
 #endif
 #ifdef HAVE_HW_SYSCLK_LWLA
 extern SR_PRIV struct sr_dev_driver sysclk_lwla_driver_info;
@@ -316,6 +317,7 @@ SR_PRIV struct sr_dev_driver *drivers_list[] = {
        &iso_tech_idm103n_driver_info,
        &tenma_72_7745_ser_driver_info,
        &tenma_72_7750_ser_driver_info,
+       &brymen_bm25x_ser_driver_info,
 #endif
 #ifdef HAVE_HW_SYSCLK_LWLA
        &sysclk_lwla_driver_info,
index b96c7a0da982c18431a2b02697843bbf71f77e38..d56a9d7a2f6475d0e206a2b750db2ed4c0cc3e74 100644 (file)
@@ -74,6 +74,7 @@ SR_PRIV struct sr_dev_driver uni_t_ut61e_ser_driver_info;
 SR_PRIV struct sr_dev_driver iso_tech_idm103n_driver_info;
 SR_PRIV struct sr_dev_driver tenma_72_7745_ser_driver_info;
 SR_PRIV struct sr_dev_driver tenma_72_7750_ser_driver_info;
+SR_PRIV struct sr_dev_driver brymen_bm25x_ser_driver_info;
 
 SR_PRIV struct dmm_info dmms[] = {
        {
@@ -318,6 +319,13 @@ SR_PRIV struct dmm_info dmms[] = {
                NULL,
                &tenma_72_7750_ser_driver_info, receive_data_TENMA_72_7750_SER,
        },
+       {
+               "Brymen", "BM25x (BC20X cable)", "9600/8n1/rts=1/dtr=1",
+               9600, BRYMEN_BM25X_PACKET_SIZE, 0, 0, NULL,
+               sr_brymen_bm25x_packet_valid, sr_brymen_bm25x_parse,
+               NULL,
+               &brymen_bm25x_ser_driver_info, receive_data_BRYMEN_BM25X_SER,
+       },
 };
 
 static int dev_clear(int dmm)
@@ -630,3 +638,4 @@ DRV(uni_t_ut61e_ser, UNI_T_UT61E_SER, "uni-t-ut61e-ser", "UNI-T UT61E (UT-D02 ca
 DRV(iso_tech_idm103n, ISO_TECH_IDM103N, "iso-tech-idm103n", "ISO-TECH IDM103N")
 DRV(tenma_72_7745_ser, TENMA_72_7745_SER, "tenma-72-7745-ser", "Tenma 72-7745 (UT-D02 cable)")
 DRV(tenma_72_7750_ser, TENMA_72_7750_SER, "tenma-72-7750-ser", "Tenma 72-7750 (UT-D02 cable)")
+DRV(brymen_bm25x_ser, BRYMEN_BM25X_SER, "brymen-bm25x-ser", "Brymen BM25x (BC20X cable)")
index 0b2472fbb70401096073d0b24399fc07e0b633d7..c812793929aab89a4d31063cb9453ddea2eb20a9 100644 (file)
@@ -223,3 +223,4 @@ RECEIVE_DATA(UNI_T_UT61E_SER, es519xx)
 RECEIVE_DATA(ISO_TECH_IDM103N, es519xx)
 RECEIVE_DATA(TENMA_72_7745_SER, fs9721)
 RECEIVE_DATA(TENMA_72_7750_SER, es519xx)
+RECEIVE_DATA(BRYMEN_BM25X_SER, bm25x)
index fb5a2a336d99e88fc11c8d618dfb144b3acfb0b5..246894675b2fbdcaafec7c99d5f98129ca183f7a 100644 (file)
@@ -56,6 +56,7 @@ enum {
        ISO_TECH_IDM103N,
        TENMA_72_7745_SER,
        TENMA_72_7750_SER,
+       BRYMEN_BM25X_SER,
 };
 
 struct dmm_info {
@@ -155,5 +156,6 @@ SR_PRIV int receive_data_UNI_T_UT61E_SER(int fd, int revents, void *cb_data);
 SR_PRIV int receive_data_ISO_TECH_IDM103N(int fd, int revents, void *cb_data);
 SR_PRIV int receive_data_TENMA_72_7745_SER(int fd, int revents, void *cb_data);
 SR_PRIV int receive_data_TENMA_72_7750_SER(int fd, int revents, void *cb_data);
+SR_PRIV int receive_data_BRYMEN_BM25X_SER(int fd, int revents, void *cb_data);
 
 #endif
index 642277ebfd47d5c1e1a95076ad841ef62a20eb03..a18ccf6ae47787a7858d687f6ded1aadcdb0d080 100644 (file)
@@ -740,4 +740,15 @@ SR_PRIV gboolean sr_rs9lcd_packet_valid(const uint8_t *buf);
 SR_PRIV int sr_rs9lcd_parse(const uint8_t *buf, float *floatval,
                            struct sr_datafeed_analog *analog, void *info);
 
+/*--- hardware/common/dmm/bm25x.c -----------------------------------------*/
+
+#define BRYMEN_BM25X_PACKET_SIZE 15
+
+/* Dummy info struct. The parser does not use it. */
+struct bm25x_info { int dummy; };
+
+SR_PRIV gboolean sr_brymen_bm25x_packet_valid(const uint8_t *buf);
+SR_PRIV int sr_brymen_bm25x_parse(const uint8_t *buf, float *floatval,
+                            struct sr_datafeed_analog *analog, void *info);
+
 #endif