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