diff --git a/Makefile.am b/Makefile.am index 62aca8ac9..8aded14dc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -184,6 +184,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..f4a5b15cb --- /dev/null +++ b/src/dmm/qm1578.c @@ -0,0 +1,210 @@ +/* + * 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 68a45f277..91bf53b3a 100644 --- a/src/libsigrok-internal.h +++ b/src/libsigrok-internal.h @@ -2557,6 +2557,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