From 85ff7750f7181317bea1758890452f5312421fd2 Mon Sep 17 00:00:00 2001 From: Mike McCauley Date: Tue, 28 Jun 2022 10:51:29 +1000 Subject: [PATCH 1/2] Added support for Digitech QM1576 serial protocol parser The protocol is described at https://www.airspayce.com/mikem/QM1578/protocol.txt You can use this decoder with libsigrok and Digitech QM1578 via ESP32 Bluetooth-Serial converter available from the author at: https://www.airspayce.com/mikem/QM1578/QM1578BluetoothClient.ino which connects to the QM1578 over Bluetooth LE, fetches the data stream and sends it on the serial port to the host, where this driver can read it with this command for example: sigrok-cli --driver digitech-qm1578:conn=/dev/ttyUSB1 --continuous --- Makefile.am | 1 + src/dmm/qm1578.c | 212 ++++++++++++++++++++++++++++++++++ src/hardware/serial-dmm/api.c | 12 +- src/libsigrok-internal.h | 10 ++ 4 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 src/dmm/qm1578.c diff --git a/Makefile.am b/Makefile.am index 280cf64d2..021ef0f80 100644 --- a/Makefile.am +++ b/Makefile.am @@ -176,6 +176,7 @@ endif # Hardware (DMM chip parsers) libsigrok_la_SOURCES += \ src/dmm/asycii.c \ + src/dmm/qm1578.c \ src/dmm/bm25x.c \ src/dmm/bm52x.c \ src/dmm/bm85x.c \ diff --git a/src/dmm/qm1578.c b/src/dmm/qm1578.c new file mode 100644 index 000000000..5caa9ef8e --- /dev/null +++ b/src/dmm/qm1578.c @@ -0,0 +1,212 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2014 Janne Huttunen + * + * 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 . + */ + +/** + * @file + * + * qm1578.c + * + * Digitech QM1576 serial protocol parser for libsigrok. + * QM1576 is a 600 count RMS DMM, with Bluetooth 4.0 support. + * https://www.jaycar.com.au/true-rms-digital-multimeter-with-bluetooth-connectivity/p/QM1578 + * + * The protocol is described at https://www.airspayce.com/mikem/QM1578/protocol.txt + * + * You can use this decoder with libsigrok and Digitech QM1578 via ESP32 Bluetooth-Serial converter + * available from the author at: + * https://www.airspayce.com/mikem/QM1578/QM1578BluetoothClient.ino + * which connects to the QM1578 over Bluetooth LE, fetches the + * data stream and sends it on the serial port to the host, where this driver can read it + * with this command for example: + * sigrok-cli --driver digitech-qm1578:conn=/dev/ttyUSB1 --continuous + * + * See https://www.airspayce.com/mikem/QM1578//README for more data + * + * Author: mikem@airspayce.com + */ + +#include +#include +#include +#include "libsigrok-internal.h" + +#define LOG_PREFIX "digitech-qm1578" + +/* Number of digits the meter supports */ +#define MAX_DIGITS 4 + +/* Decode the multiplier prefix from buf[11] */ +static int decode_prefix(const uint8_t *buf) +{ + switch (buf[11]) { + case 0x00: return 0; + case 0x01: return 3; + case 0x02: return 6; + case 0x03: return -9; + case 0x04: return -6; + case 0x05: return -3; /* For amps */ + case 0x06: return -3; /* For volts */ + default: + sr_dbg("Unknown multiplier: 0x%02x.", buf[11]); + return -1; + } +} + +static float decode_value(const uint8_t *buf, int *exponent) +{ + float val = 0.0f; + int i, digit; + + /* On overload, digits 4 to 1 are: 0x0b 0x0a 0x00 0x0b */ + if (buf[8] == 0x0b) + return INFINITY; + + /* Decode the 4 digits */ + for (i = 0; i < MAX_DIGITS; i++) { + digit = buf[8 - i]; + if (digit < 0 || digit > 9) + continue; + val = 10.0 * val + digit; + } + + *exponent = -buf[9]; + return val; +} + +SR_PRIV gboolean sr_digitech_qm1578_packet_valid(const uint8_t *buf) +{ + /* + * First 4 digits on my meter are always 0d5 0xf0 0x00 0x0a + * Dont know if thats the same for all meters or just mine, so ignore them + * and just use the presence of the trailing record separator + */ + if (buf[14] != 0x0d) + return FALSE; + + return TRUE; +} + +SR_PRIV int sr_digitech_qm1578_parse(const uint8_t *buf, float *floatval, + struct sr_datafeed_analog *analog, void *info) +{ + int exponent = 0; + float val; + + (void)info; + + /* serial-dmm will dump the contents of packet by using -l 4 */ + + /* Defaults */ + analog->meaning->mq = SR_MQ_GAIN; + analog->meaning->unit = SR_UNIT_UNITLESS; + analog->meaning->mqflags = 0; + + /* Decode sone flags */ + if (buf[13] & 0x10) + analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE; + if (buf[13] & 0x40) + analog->meaning->mqflags |= SR_MQFLAG_DC; + if (buf[13] & 0x80) + analog->meaning->mqflags |= SR_MQFLAG_AC; + if (buf[13] & 0x20) + analog->meaning->mqflags |= SR_MQFLAG_RELATIVE; + if (buf[12] & 0x40) + analog->meaning->mqflags |= SR_MQFLAG_HOLD; + if ((buf[13] & 0x0c) == 0x0c) + analog->meaning->mqflags |= SR_MQFLAG_MAX; + if ((buf[13] & 0x0c) == 0x08) + analog->meaning->mqflags |= SR_MQFLAG_MIN; + if ((buf[13] & 0x0c) == 0x0c) + analog->meaning->mqflags |= SR_MQFLAG_AVG; + + /* Decode the meter setting. Caution: there may be others on other meters: hFE? */ + if (buf[4] == 0x01) { + analog->meaning->mq = SR_MQ_VOLTAGE; + analog->meaning->unit = SR_UNIT_VOLT; + analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS; + } + if (buf[4] == 0x02) { + analog->meaning->mq = SR_MQ_VOLTAGE; + analog->meaning->unit = SR_UNIT_VOLT; + analog->meaning->mqflags |= SR_MQFLAG_DC; + } + /* what is 03 ? */ + if (buf[4] == 0x04) { + analog->meaning->mq = SR_MQ_RESISTANCE; + analog->meaning->unit = SR_UNIT_OHM; + } + if (buf[4] == 0x05) { + analog->meaning->mq = SR_MQ_CAPACITANCE; + analog->meaning->unit = SR_UNIT_FARAD; + } + if (buf[4] == 0x06) { + analog->meaning->mq = SR_MQ_TEMPERATURE; + if (buf[10] == 0x08) + analog->meaning->unit = SR_UNIT_CELSIUS; + else + analog->meaning->unit = SR_UNIT_FAHRENHEIT; + } + if (buf[4] == 0x07 || buf[4] == 0x08 ||buf[4] == 0x09) { + analog->meaning->mq = SR_MQ_CURRENT; + analog->meaning->unit = SR_UNIT_AMPERE; + analog->meaning->mqflags |= SR_MQFLAG_DC; + } + /* 0x0a ? 0x0b? */ + if (buf[4] == 0x0c || buf[4] == 0x0d || buf[4] == 0x0e) { + analog->meaning->mq = SR_MQ_CURRENT; + analog->meaning->unit = SR_UNIT_AMPERE; + analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS; + } + if (buf[4] == 0x0f) { + analog->meaning->mq = SR_MQ_VOLTAGE; + analog->meaning->unit = SR_UNIT_VOLT; + analog->meaning->mqflags |= SR_MQFLAG_DIODE; + } + if (buf[4] == 0x10) { + if (buf[10] == 0x04) + { + analog->meaning->mq = SR_MQ_FREQUENCY; + analog->meaning->unit = SR_UNIT_HERTZ; + } + else + { + analog->meaning->mq = SR_MQ_DUTY_CYCLE; + analog->meaning->unit = SR_UNIT_PERCENTAGE; + } + } + if (buf[4] == 0x20) { + analog->meaning->mq = SR_MQ_CONTINUITY; + analog->meaning->unit = SR_UNIT_OHM; + } + + + val = decode_value(buf, &exponent); + exponent += decode_prefix(buf); + val *= powf(10, exponent); + + if (buf[12] & 0x80) + val = -val; + + *floatval = val; + analog->encoding->digits = -exponent; + analog->spec->spec_digits = -exponent; + + return SR_OK; + +} diff --git a/src/hardware/serial-dmm/api.c b/src/hardware/serial-dmm/api.c index 9af2bd5bc..b271b81c0 100644 --- a/src/hardware/serial-dmm/api.c +++ b/src/hardware/serial-dmm/api.c @@ -60,7 +60,6 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options) char ch_name[12]; dmm = (struct dmm_info *)di; - conn = dmm->conn; serialcomm = dmm->serialcomm; for (l = options; l; l = l->next) { @@ -356,7 +355,16 @@ SR_REGISTER_DEV_DRIVER_LIST(serial_dmm_drivers, * Fold marks {{{ }}} with matching braces were added, to further * speed up navigation in the long list. */ - /* asycii based meters {{{ */ + + DMM( + "digitech-qm1578", qm1578, + "Digitech", "QM1578", "115200/8n1", + DIGITECH_QM1578_PACKET_SIZE, 0, 0, NULL, + sr_digitech_qm1578_packet_valid, sr_digitech_qm1578_parse, + NULL + ), + + /* asycii based meters {{{ */ DMM( "metrix-mx56c", asycii, "Metrix", "MX56C", "2400/8n1", ASYCII_PACKET_SIZE, 0, 0, NULL, diff --git a/src/libsigrok-internal.h b/src/libsigrok-internal.h index db74dcc27..b3f080119 100644 --- a/src/libsigrok-internal.h +++ b/src/libsigrok-internal.h @@ -2472,6 +2472,16 @@ 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); +/*--- dmm/qm1578.c -----------------------------------------------------------*/ + +#define DIGITECH_QM1578_PACKET_SIZE 15 + +/* Dummy info struct. The parser does not use it. */ +struct qm1578_info { int dummy; }; + +SR_PRIV gboolean sr_digitech_qm1578_packet_valid(const uint8_t *buf); +SR_PRIV int sr_digitech_qm1578_parse(const uint8_t *buf, float *floatval, + struct sr_datafeed_analog *analog, void *info); /*--- dmm/bm25x.c -----------------------------------------------------------*/ #define BRYMEN_BM25X_PACKET_SIZE 15 From a33f19d9d96ed24936c93b1d144d832aa8443a8f Mon Sep 17 00:00:00 2001 From: Mike McCauley Date: Wed, 29 Jun 2022 10:39:38 +1000 Subject: [PATCH 2/2] Fixed indenting per review comments --- src/dmm/qm1578.c | 268 +++++++++++++++++++++++------------------------ 1 file changed, 133 insertions(+), 135 deletions(-) diff --git a/src/dmm/qm1578.c b/src/dmm/qm1578.c index 5caa9ef8e..c1ed95176 100644 --- a/src/dmm/qm1578.c +++ b/src/dmm/qm1578.c @@ -54,159 +54,157 @@ /* Decode the multiplier prefix from buf[11] */ static int decode_prefix(const uint8_t *buf) { - switch (buf[11]) { - case 0x00: return 0; - case 0x01: return 3; - case 0x02: return 6; - case 0x03: return -9; - case 0x04: return -6; - case 0x05: return -3; /* For amps */ - case 0x06: return -3; /* For volts */ - default: - sr_dbg("Unknown multiplier: 0x%02x.", buf[11]); - return -1; - } + switch (buf[11]) { + case 0x00: return 0; + case 0x01: return 3; + case 0x02: return 6; + case 0x03: return -9; + case 0x04: return -6; + case 0x05: return -3; /* For amps */ + case 0x06: return -3; /* For volts */ + default: + sr_dbg("Unknown multiplier: 0x%02x.", buf[11]); + return -1; + } } static float decode_value(const uint8_t *buf, int *exponent) { - float val = 0.0f; - int i, digit; - - /* On overload, digits 4 to 1 are: 0x0b 0x0a 0x00 0x0b */ - if (buf[8] == 0x0b) - return INFINITY; - - /* Decode the 4 digits */ - for (i = 0; i < MAX_DIGITS; i++) { - digit = buf[8 - i]; - if (digit < 0 || digit > 9) - continue; - val = 10.0 * val + digit; - } - - *exponent = -buf[9]; - return val; + float val = 0.0f; + int i, digit; + + /* On overload, digits 4 to 1 are: 0x0b 0x0a 0x00 0x0b */ + if (buf[8] == 0x0b) + return INFINITY; + + /* Decode the 4 digits */ + for (i = 0; i < MAX_DIGITS; i++) { + digit = buf[8 - i]; + if (digit < 0 || digit > 9) + continue; + val = 10.0 * val + digit; + } + + *exponent = -buf[9]; + return val; } SR_PRIV gboolean sr_digitech_qm1578_packet_valid(const uint8_t *buf) { - /* - * First 4 digits on my meter are always 0d5 0xf0 0x00 0x0a - * Dont know if thats the same for all meters or just mine, so ignore them - * and just use the presence of the trailing record separator - */ - if (buf[14] != 0x0d) - return FALSE; - - return TRUE; + /* + * First 4 digits on my meter are always 0d5 0xf0 0x00 0x0a + * Dont know if thats the same for all meters or just mine, so ignore them + * and just use the presence of the trailing record separator + */ + if (buf[14] != 0x0d) + return FALSE; + + return TRUE; } SR_PRIV int sr_digitech_qm1578_parse(const uint8_t *buf, float *floatval, - struct sr_datafeed_analog *analog, void *info) + struct sr_datafeed_analog *analog, void *info) { - int exponent = 0; - float val; - - (void)info; - - /* serial-dmm will dump the contents of packet by using -l 4 */ - - /* Defaults */ - analog->meaning->mq = SR_MQ_GAIN; - analog->meaning->unit = SR_UNIT_UNITLESS; - analog->meaning->mqflags = 0; - - /* Decode sone flags */ - if (buf[13] & 0x10) - analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE; - if (buf[13] & 0x40) - analog->meaning->mqflags |= SR_MQFLAG_DC; - if (buf[13] & 0x80) - analog->meaning->mqflags |= SR_MQFLAG_AC; - if (buf[13] & 0x20) - analog->meaning->mqflags |= SR_MQFLAG_RELATIVE; - if (buf[12] & 0x40) - analog->meaning->mqflags |= SR_MQFLAG_HOLD; - if ((buf[13] & 0x0c) == 0x0c) - analog->meaning->mqflags |= SR_MQFLAG_MAX; - if ((buf[13] & 0x0c) == 0x08) - analog->meaning->mqflags |= SR_MQFLAG_MIN; - if ((buf[13] & 0x0c) == 0x0c) - analog->meaning->mqflags |= SR_MQFLAG_AVG; - - /* Decode the meter setting. Caution: there may be others on other meters: hFE? */ - if (buf[4] == 0x01) { - analog->meaning->mq = SR_MQ_VOLTAGE; - analog->meaning->unit = SR_UNIT_VOLT; - analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS; - } - if (buf[4] == 0x02) { - analog->meaning->mq = SR_MQ_VOLTAGE; - analog->meaning->unit = SR_UNIT_VOLT; - analog->meaning->mqflags |= SR_MQFLAG_DC; - } - /* what is 03 ? */ - if (buf[4] == 0x04) { - analog->meaning->mq = SR_MQ_RESISTANCE; - analog->meaning->unit = SR_UNIT_OHM; - } - if (buf[4] == 0x05) { - analog->meaning->mq = SR_MQ_CAPACITANCE; - analog->meaning->unit = SR_UNIT_FARAD; - } - if (buf[4] == 0x06) { - analog->meaning->mq = SR_MQ_TEMPERATURE; - if (buf[10] == 0x08) - analog->meaning->unit = SR_UNIT_CELSIUS; - else - analog->meaning->unit = SR_UNIT_FAHRENHEIT; - } - if (buf[4] == 0x07 || buf[4] == 0x08 ||buf[4] == 0x09) { - analog->meaning->mq = SR_MQ_CURRENT; - analog->meaning->unit = SR_UNIT_AMPERE; - analog->meaning->mqflags |= SR_MQFLAG_DC; - } - /* 0x0a ? 0x0b? */ - if (buf[4] == 0x0c || buf[4] == 0x0d || buf[4] == 0x0e) { - analog->meaning->mq = SR_MQ_CURRENT; - analog->meaning->unit = SR_UNIT_AMPERE; - analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS; - } - if (buf[4] == 0x0f) { - analog->meaning->mq = SR_MQ_VOLTAGE; - analog->meaning->unit = SR_UNIT_VOLT; - analog->meaning->mqflags |= SR_MQFLAG_DIODE; - } - if (buf[4] == 0x10) { - if (buf[10] == 0x04) - { - analog->meaning->mq = SR_MQ_FREQUENCY; - analog->meaning->unit = SR_UNIT_HERTZ; + int exponent = 0; + float val; + + (void)info; + + /* serial-dmm will dump the contents of packet by using -l 4 */ + + /* Defaults */ + analog->meaning->mq = SR_MQ_GAIN; + analog->meaning->unit = SR_UNIT_UNITLESS; + analog->meaning->mqflags = 0; + + /* Decode sone flags */ + if (buf[13] & 0x10) + analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE; + if (buf[13] & 0x40) + analog->meaning->mqflags |= SR_MQFLAG_DC; + if (buf[13] & 0x80) + analog->meaning->mqflags |= SR_MQFLAG_AC; + if (buf[13] & 0x20) + analog->meaning->mqflags |= SR_MQFLAG_RELATIVE; + if (buf[12] & 0x40) + analog->meaning->mqflags |= SR_MQFLAG_HOLD; + if ((buf[13] & 0x0c) == 0x0c) + analog->meaning->mqflags |= SR_MQFLAG_MAX; + if ((buf[13] & 0x0c) == 0x08) + analog->meaning->mqflags |= SR_MQFLAG_MIN; + if ((buf[13] & 0x0c) == 0x0c) + analog->meaning->mqflags |= SR_MQFLAG_AVG; + + /* Decode the meter setting. Caution: there may be others on other meters: hFE? */ + if (buf[4] == 0x01) { + analog->meaning->mq = SR_MQ_VOLTAGE; + analog->meaning->unit = SR_UNIT_VOLT; + analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS; + } + if (buf[4] == 0x02) { + analog->meaning->mq = SR_MQ_VOLTAGE; + analog->meaning->unit = SR_UNIT_VOLT; + analog->meaning->mqflags |= SR_MQFLAG_DC; + } + /* what is 03 ? */ + if (buf[4] == 0x04) { + analog->meaning->mq = SR_MQ_RESISTANCE; + analog->meaning->unit = SR_UNIT_OHM; + } + if (buf[4] == 0x05) { + analog->meaning->mq = SR_MQ_CAPACITANCE; + analog->meaning->unit = SR_UNIT_FARAD; + } + if (buf[4] == 0x06) { + analog->meaning->mq = SR_MQ_TEMPERATURE; + if (buf[10] == 0x08) + analog->meaning->unit = SR_UNIT_CELSIUS; + else + analog->meaning->unit = SR_UNIT_FAHRENHEIT; + } + if (buf[4] == 0x07 || buf[4] == 0x08 ||buf[4] == 0x09) { + analog->meaning->mq = SR_MQ_CURRENT; + analog->meaning->unit = SR_UNIT_AMPERE; + analog->meaning->mqflags |= SR_MQFLAG_DC; + } + /* 0x0a ? 0x0b? */ + if (buf[4] == 0x0c || buf[4] == 0x0d || buf[4] == 0x0e) { + analog->meaning->mq = SR_MQ_CURRENT; + analog->meaning->unit = SR_UNIT_AMPERE; + analog->meaning->mqflags |= SR_MQFLAG_AC | SR_MQFLAG_RMS; + } + if (buf[4] == 0x0f) { + analog->meaning->mq = SR_MQ_VOLTAGE; + analog->meaning->unit = SR_UNIT_VOLT; + analog->meaning->mqflags |= SR_MQFLAG_DIODE; + } + if (buf[4] == 0x10) { + if (buf[10] == 0x04) { + analog->meaning->mq = SR_MQ_FREQUENCY; + analog->meaning->unit = SR_UNIT_HERTZ; + } + else { + analog->meaning->mq = SR_MQ_DUTY_CYCLE; + analog->meaning->unit = SR_UNIT_PERCENTAGE; + } } - else - { - analog->meaning->mq = SR_MQ_DUTY_CYCLE; - analog->meaning->unit = SR_UNIT_PERCENTAGE; + if (buf[4] == 0x20) { + analog->meaning->mq = SR_MQ_CONTINUITY; + analog->meaning->unit = SR_UNIT_OHM; } - } - if (buf[4] == 0x20) { - analog->meaning->mq = SR_MQ_CONTINUITY; - analog->meaning->unit = SR_UNIT_OHM; - } - val = decode_value(buf, &exponent); - exponent += decode_prefix(buf); - val *= powf(10, exponent); + val = decode_value(buf, &exponent); + exponent += decode_prefix(buf); + val *= powf(10, exponent); - if (buf[12] & 0x80) - val = -val; + if (buf[12] & 0x80) + val = -val; - *floatval = val; - analog->encoding->digits = -exponent; - analog->spec->spec_digits = -exponent; + *floatval = val; + analog->encoding->digits = -exponent; + analog->spec->spec_digits = -exponent; - return SR_OK; + return SR_OK; }