Hi all,
after the Meterman 38XR driver has been published recently I had a look
at it and found that it could be taken as a good starting point for a
driver for the Mastech M9803R DMM which is in status "planned" on the
hardware pages.

I know that the preferred way for contributions is pushing to a git
repo. But as I am not that fluent with git: would it be possible to use
the attached patch?


If you think this driver should be added to sigrok I would also like to
add some remarks to the (existing) wiki page. For this I would need to
request an account: can this been done on this list?


Regards,
Thomas Hoffmann
diff -Naur libsigrok/Makefile.am libsigrok.new/Makefile.am
--- libsigrok/Makefile.am	2021-01-16 15:48:17.078146731 +0100
+++ libsigrok.new/Makefile.am	2021-01-11 22:15:01.533227460 +0100
@@ -178,6 +178,7 @@
 	src/dmm/mm38xr.c \
 	src/dmm/ms2115b.c \
 	src/dmm/ms8250d.c \
+	src/dmm/ms9803r.c \
 	src/dmm/rs9lcd.c \
 	src/dmm/ut372.c \
 	src/dmm/ut71x.c \
diff -Naur libsigrok/src/dmm/ms9803r.c libsigrok.new/src/dmm/ms9803r.c
--- libsigrok/src/dmm/ms9803r.c	1970-01-01 01:00:00.000000000 +0100
+++ libsigrok.new/src/dmm/ms9803r.c	2021-01-16 17:07:48.201328312 +0100
@@ -0,0 +1,350 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2021 Thomas Hoffmann <th.hoffm...@mailbox.org>
+ * based on Meterman 38XR driver:
+ * Copyright (C) 2020 Peter Skarpetis <pet...@skarpetis.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/>.
+ */
+
+/*
+ * Mastech 9803R protocol parser
+ *
+ * Communication parameters: Unidirectional, 9600/8n1 (contrary to official documentation)
+ *
+ * The user guide can be downloaded from:
+ * http://www.mastech-group.com/products.php?cate=94&PNo=51
+ * or
+ * https://www.pollin.de/productdownloads/D830256B.PDF
+ *
+ * Protocol is described here:
+ * http://www.mtoussaint.de/cdmm/doc/html/classM9803R.html
+ * or here:
+ * http://www.expertprofi.ag.vu/m-980t.html
+ * 
+ * 
+ * There is also a disussion about the hardware and the protocol at this site (in German),
+ * also a note on the RMS method:
+ * https://www.reinhardweiss.de/german/mastech.htm
+ *
+ * 
+ * A program to read the values from the RS232 interface of the M9803R desktop multimeter 
+ * and provide them in readable format for use with gnuplot, scripts, etc.:
+ * https://sourceforge.net/projects/mmeter-read/   
+ */
+
+/**
+ * @file
+ *
+ * Mastech 9803R BCD protocol parser.
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+#include <math.h>
+#include <string.h>
+
+#define LOG_PREFIX "ms9803r"
+
+enum ms9803r_func_code {
+	FUNC_CODE_VOLTS_DC = 0x00,
+	FUNC_CODE_VOLTS_AC = 0x01,
+	FUNC_CODE_CURRENT_MAMPS_DC = 0x02,
+	FUNC_CODE_CURRENT_MAMPS_AC = 0x03,
+	FUNC_CODE_RESISTANCE_OHMS = 0x04,
+	FUNC_CODE_BUZZER = 0x05,              // 400 Ohm range + sound for R<40 Ohm
+	FUNC_CODE_DIODE_TEST = 0x06,          // 4 V range
+	FUNC_CODE_ADAPTIVE = 0x07,            // 400 mV range (in Freq mode, blue button pressed)
+	FUNC_CODE_CURRENT_10_AMPS_DC = 0x08,
+	FUNC_CODE_CURRENT_10_AMPS_AC = 0x09,
+	FUNC_CODE_FREQUENCY_HZ = 0x0a, 
+	FUNC_CODE_UNUSED = 0x0b,
+	FUNC_CODE_CAPACITANCE = 0x0c,
+};
+
+enum ms9803r_meas_mode {
+	/* This is used to index into the digits and exponent arrays below. */
+	MEAS_MODE_VOLTS,
+	MEAS_MODE_RESISTANCE_OHMS,
+	MEAS_MODE_CURRENT_MAMPS, /* mA */
+	MEAS_MODE_CURRENT_AMPS,
+	MEAS_MODE_CAPACITANCE,
+	MEAS_MODE_DIODE_TEST,
+	MEAS_MODE_FREQUENCY_HZ,
+	MEAS_MODE_CONTINUITY,
+	/* For internal purposes. */
+	MEAS_MODE_UNDEFINED,
+};
+
+enum ms9803r_acdc_mode {
+	ACDC_MODE_NONE = 1000,
+	ACDC_MODE_DC,
+	ACDC_MODE_AC,
+};
+
+struct mastech_info {
+	unsigned int spfun0;                 // byte 0: 8XXX<+/-><BattLow>X<Overload>
+	unsigned int reading;                // byte 1,2,3,4; LCD digits
+	enum ms9803r_func_code functioncode; // byte 5 & 0x7f
+	size_t rangecode;                    // byte 6 & 0x7f
+	unsigned int spfun1;                 // byte 7: 8XXX<max><min><rel><hold>
+	unsigned int spfun2;                 // byte 8: 8XXX<mem><auto><manl><apof>
+
+	/* calculated values */
+	enum ms9803r_meas_mode meas_mode;
+	enum ms9803r_acdc_mode acdc;
+};
+
+static const int decimal_digits[][7] = {
+	[MEAS_MODE_VOLTS]           = { 1, 3, 2, 1, 0, 0, 0, },
+	[MEAS_MODE_RESISTANCE_OHMS] = { 1, 3, 2, 1, 0, 2, 0, },
+	[MEAS_MODE_CURRENT_MAMPS]   = { 3, 2, 1, 0, 0, 0, 0, },
+	[MEAS_MODE_CURRENT_AMPS]    = { 2, 0, 0, 0, 0, 0, 0, },
+	[MEAS_MODE_CAPACITANCE]     = { 3, 2, 1, 3, 2, 0, 0, },
+	[MEAS_MODE_DIODE_TEST]      = { 0, 3, 0, 0, 0, 0, 0, },
+	[MEAS_MODE_FREQUENCY_HZ]    = { 3, 2, 1, 0, 0, 2, 1, },
+	[MEAS_MODE_CONTINUITY]      = { 1, 0, 0, 0, 0, 1, 0, },
+};
+
+static const int units_exponents[][7] = {
+	[MEAS_MODE_VOLTS]           = { -3,  0,  0,  0,  0,  0,  0, },
+	[MEAS_MODE_RESISTANCE_OHMS] = {  0,  3,  3,  3,  6,  0,  0, },
+	[MEAS_MODE_CURRENT_MAMPS]   = { -3, -3, -3,  0,  0,  0,  0, },
+	[MEAS_MODE_CURRENT_AMPS]    = {  0,  0,  0,  0,  0,  0,  0, },
+	[MEAS_MODE_CAPACITANCE]     = { -9, -9, -9, -6, -6,  0,  0, },
+	[MEAS_MODE_DIODE_TEST]      = {  0,  0,  0,  0,  0,  0,  0, },
+	[MEAS_MODE_FREQUENCY_HZ]    = {  3,  3,  3,  0,  0,  0,  0, },
+	[MEAS_MODE_CONTINUITY]      = {  0,  0,  0,  0,  0,  0,  0, },
+};
+
+static uint32_t mastech_9803r_func_code(const uint8_t *buf)
+{
+	return (uint32_t)(buf[5] & 0x7f);
+}
+
+static uint32_t mastech_9803r_reading(const uint8_t *buf)
+{
+	uint32_t v;
+
+	v = buf[4] * 1000 + buf[3] * 100 + buf[2] * 10 + buf[1];
+
+	return v;
+}
+
+static gboolean mastech_9803r_is_negative(struct mastech_info *mi)
+{
+	return mi->spfun0 & 8;
+}
+
+static int mastech_9803r_decode(const uint8_t *buf, struct mastech_info *mi)
+{
+
+	if (!mastech_9803r_packet_valid(buf))
+		return SR_ERR;
+
+	mi->spfun0 = buf[0] & 0x7f;
+	mi->functioncode = mastech_9803r_func_code(buf);
+	if (mi->functioncode == 0x0B || mi->functioncode > 0x0C)
+		return SR_ERR;
+	mi->reading = mastech_9803r_reading(buf);
+	mi->rangecode = buf[6] & 0x7f;
+	if (mi->rangecode > 6)
+		return SR_ERR;
+	mi->spfun1 = buf[7] & 0x7f; 
+	mi->spfun2 = buf[8] & 0x7f; 
+
+	mi->acdc = ACDC_MODE_NONE;
+
+	switch (mi->functioncode) {
+	case FUNC_CODE_VOLTS_DC:
+		mi->meas_mode = MEAS_MODE_VOLTS;
+		mi->acdc = ACDC_MODE_DC;
+		break;
+
+	case FUNC_CODE_VOLTS_AC:
+		mi->meas_mode = MEAS_MODE_VOLTS;
+		mi->acdc = ACDC_MODE_AC;
+		break;
+
+	case FUNC_CODE_CURRENT_MAMPS_DC:
+		mi->meas_mode = MEAS_MODE_CURRENT_MAMPS;
+		mi->acdc = ACDC_MODE_DC;
+		break;
+
+	case FUNC_CODE_CURRENT_MAMPS_AC:
+		mi->meas_mode = MEAS_MODE_CURRENT_MAMPS;
+		mi->acdc = ACDC_MODE_DC;
+		break;
+
+	case FUNC_CODE_BUZZER:
+		mi->meas_mode = MEAS_MODE_CONTINUITY;
+		break;
+
+	case FUNC_CODE_DIODE_TEST:
+		mi->meas_mode = MEAS_MODE_DIODE_TEST;
+		mi->acdc = ACDC_MODE_DC;
+		break;
+
+	case FUNC_CODE_ADAPTIVE:
+		mi->meas_mode = MEAS_MODE_VOLTS;
+		mi->acdc = ACDC_MODE_DC;
+		break;
+
+	case FUNC_CODE_CURRENT_10_AMPS_DC:
+		mi->meas_mode = MEAS_MODE_CURRENT_AMPS;
+		mi->acdc = ACDC_MODE_DC;
+		break;
+
+	case FUNC_CODE_CURRENT_10_AMPS_AC:
+		mi->meas_mode = MEAS_MODE_CURRENT_AMPS;
+		mi->acdc = ACDC_MODE_AC;
+		break;
+
+	case FUNC_CODE_FREQUENCY_HZ:
+		mi->meas_mode = MEAS_MODE_FREQUENCY_HZ;
+		break;
+
+	case FUNC_CODE_CAPACITANCE:
+		mi->meas_mode = MEAS_MODE_CAPACITANCE;
+		break;
+
+	default:
+		mi->meas_mode = MEAS_MODE_UNDEFINED;
+		return SR_ERR;
+
+	}
+	return SR_OK;
+}
+
+SR_PRIV gboolean mastech_9803r_packet_valid(const uint8_t *buf)
+{
+	size_t i;
+	uint32_t fcode;
+
+	if ((buf[9] != '\r') || (buf[10] != '\n'))
+		return FALSE;
+
+	/* Check the 3 lower value bytes: 0 ... 9 */
+	for (i = 1; i < 4; i++)
+		if (buf[i] > 9)
+			return FALSE;
+    
+	// MSB: 0 ... 4 
+	if (buf[4] > 4)
+		return FALSE;
+	
+	fcode = mastech_9803r_func_code(buf);
+	if (fcode == 0x0B || fcode > 0x0C)
+		return FALSE;
+
+	return TRUE;
+}
+
+SR_PRIV int mastech_9803r_parse(const uint8_t *buf, float *floatval,
+	struct sr_datafeed_analog *analog, void *info)
+{
+	gboolean is_overload;
+	int exponent;
+	int digits;
+	struct mastech_info mi;
+
+	(void)info;
+
+	if (mastech_9803r_decode(buf, &mi) != SR_OK)
+		return SR_ERR;
+
+	if (mi.meas_mode != MEAS_MODE_CONTINUITY) {
+		is_overload = mi.spfun0 & 1;
+		if (is_overload) {
+			sr_spew("Over limit.");
+			*floatval = INFINITY; /* overload */
+			return SR_OK;
+		}
+	}
+	switch (mi.meas_mode) {
+	case MEAS_MODE_VOLTS:
+		analog->meaning->mq = SR_MQ_VOLTAGE;
+		analog->meaning->unit = SR_UNIT_VOLT;
+		break;
+	case MEAS_MODE_RESISTANCE_OHMS:
+		analog->meaning->mq = SR_MQ_RESISTANCE;
+		analog->meaning->unit = SR_UNIT_OHM;
+		break;
+	case MEAS_MODE_CURRENT_MAMPS:
+	case MEAS_MODE_CURRENT_AMPS:
+		analog->meaning->mq = SR_MQ_CURRENT;
+		analog->meaning->unit = SR_UNIT_AMPERE;
+		break;
+	case MEAS_MODE_CAPACITANCE:
+		analog->meaning->mq = SR_MQ_CAPACITANCE;
+		analog->meaning->unit = SR_UNIT_FARAD;
+		break;
+	case MEAS_MODE_DIODE_TEST:
+		analog->meaning->mq = SR_MQ_VOLTAGE;
+		analog->meaning->unit = SR_UNIT_VOLT;
+		analog->meaning->mqflags |= SR_MQFLAG_DIODE;
+		break;
+	case MEAS_MODE_FREQUENCY_HZ:
+		analog->meaning->mq = SR_MQ_FREQUENCY;
+		analog->meaning->unit = SR_UNIT_HERTZ;
+		break;
+	case MEAS_MODE_CONTINUITY:
+		analog->meaning->mq = SR_MQ_CONTINUITY;
+		analog->meaning->unit = SR_UNIT_BOOLEAN;
+		*floatval = (mi.spfun0 & 1) ? 0.0 : 1.0;
+		break;
+	default:
+		return SR_ERR;
+	}
+	switch (mi.acdc) {
+	case ACDC_MODE_DC:
+		analog->meaning->mqflags |= SR_MQFLAG_DC;
+		break;
+	case ACDC_MODE_AC:
+		analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS;
+		break;
+	default:
+		break;
+	}
+	if (mi.spfun1 & 1)
+		analog->meaning->mqflags |= SR_MQFLAG_HOLD;
+	if (mi.spfun1 & 2)
+		analog->meaning->mqflags |= SR_MQFLAG_RELATIVE;
+	if (mi.spfun1 & 4)
+		analog->meaning->mqflags |= SR_MQFLAG_MIN;
+	if (mi.spfun1 & 8)
+		analog->meaning->mqflags |= SR_MQFLAG_MAX;
+	if (mi.spfun2 & 4)
+		analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE;
+	if (mi.meas_mode != MEAS_MODE_CONTINUITY) {
+		digits = decimal_digits[mi.meas_mode][mi.rangecode];
+		exponent = units_exponents[mi.meas_mode][mi.rangecode];
+
+		*floatval = mi.reading;
+		if (mastech_9803r_is_negative(&mi)) {
+			*floatval *= -1.0f;
+		}
+		*floatval *= powf(10, -digits);
+		*floatval *= powf(10, exponent);
+	}
+	analog->encoding->digits = 4;
+	analog->spec->spec_digits = 4;
+
+	return SR_OK;
+}
diff -Naur libsigrok/src/hardware/serial-dmm/api.c libsigrok.new/src/hardware/serial-dmm/api.c
--- libsigrok/src/hardware/serial-dmm/api.c	2021-01-16 15:48:18.168101462 +0100
+++ libsigrok.new/src/hardware/serial-dmm/api.c	2021-01-12 21:58:38.709632956 +0100
@@ -628,6 +628,14 @@
 		NULL
 	),
 	/* }}} */
+	/* mastech_9803r based meters {{{ */
+	DMM(
+		"mastech-m9803r", mastech_9803r,
+		"MASTECH", "M9803R", "9600/8n1/rts=0/dtr=1",
+		MASTECH_9803R_PACKET_SIZE, 0, 0, NULL,
+		mastech_9803r_packet_valid, mastech_9803r_parse,
+		NULL
+	),
 	/* metex14 based meters {{{ */
 	DMM(
 		"mastech-mas345", metex14,
diff -Naur libsigrok/src/libsigrok-internal.h libsigrok.new/src/libsigrok-internal.h
--- libsigrok/src/libsigrok-internal.h	2021-01-16 15:48:18.438090251 +0100
+++ libsigrok.new/src/libsigrok-internal.h	2021-01-16 17:03:12.942649484 +0100
@@ -2261,6 +2261,16 @@
 SR_PRIV int meterman_38xr_parse(const uint8_t *buf, float *floatval,
 	struct sr_datafeed_analog *analog, void *info);
 
+/*--- dmm/ms9803r.c ---------------------------------------------------------*/
+
+#define MASTECH_9803R_PACKET_SIZE 11
+
+struct mastech_9803r_info { int dummy; };
+
+SR_PRIV gboolean mastech_9803r_packet_valid(const uint8_t *buf);
+SR_PRIV int mastech_9803r_parse(const uint8_t *buf, float *floatval,
+	struct sr_datafeed_analog *analog, void *info);
+
 /*--- dmm/ms2115b.c ---------------------------------------------------------*/
 
 #define MS2115B_PACKET_SIZE 9
_______________________________________________
sigrok-devel mailing list
sigrok-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/sigrok-devel

Reply via email to