diff --git a/Makefile.am b/Makefile.am index 0695ee08..31e4fba0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -514,6 +514,12 @@ src_libdrivers_la_SOURCES += \ src/hardware/rdtech-dps/protocol.c \ src/hardware/rdtech-dps/api.c endif +if HW_RDTECH_UM +src_libdrivers_la_SOURCES += \ + src/hardware/rdtech-um/protocol.h \ + src/hardware/rdtech-um/protocol.c \ + src/hardware/rdtech-um/api.c +endif if HW_RIGOL_DS src_libdrivers_la_SOURCES += \ src/hardware/rigol-ds/protocol.h \ diff --git a/configure.ac b/configure.ac index 4216e4f6..abd8d056 100644 --- a/configure.ac +++ b/configure.ac @@ -302,6 +302,7 @@ SR_DRIVER([OpenBench Logic Sniffer], [openbench-logic-sniffer], [serial_comm]) SR_DRIVER([PCE PCE-322A], [pce-322a], [serial_comm]) SR_DRIVER([Pipistrello-OLS], [pipistrello-ols], [libftdi]) SR_DRIVER([RDTech DPSxxxx/DPHxxxx], [rdtech-dps], [serial_comm]) +SR_DRIVER([RDTech UMXX], [rdtech-um], [serial_comm]) SR_DRIVER([Rigol DS], [rigol-ds]) SR_DRIVER([Rohde&Schwarz SME-0x], [rohde-schwarz-sme-0x], [serial_comm]) SR_DRIVER([Saleae Logic16], [saleae-logic16], [libusb]) diff --git a/src/hardware/rdtech-um/api.c b/src/hardware/rdtech-um/api.c new file mode 100644 index 00000000..fbc38b1a --- /dev/null +++ b/src/hardware/rdtech-um/api.c @@ -0,0 +1,175 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2018-2020 Andreas Sandberg + * + * 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 . + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include "libsigrok-internal.h" + +#include "protocol.h" + +#define RDTECH_UM_SERIALCOMM "115200/8n1" + +static const uint32_t scanopts[] = { + SR_CONF_CONN, + SR_CONF_SERIALCOMM, +}; + +static const uint32_t drvopts[] = { + SR_CONF_ENERGYMETER, +}; + +static const uint32_t devopts[] = { + SR_CONF_CONTINUOUS, + SR_CONF_LIMIT_SAMPLES | SR_CONF_SET, + SR_CONF_LIMIT_MSEC | SR_CONF_SET, +}; + +static GSList *rdtech_um_scan(struct sr_dev_driver *di, const char *conn, + const char *serialcomm) +{ + struct sr_serial_dev_inst *serial; + const struct rdtech_um_profile *p = NULL; + GSList *devices = NULL; + struct dev_context *devc = NULL; + struct sr_dev_inst *sdi = NULL; + + serial = sr_serial_dev_inst_new(conn, serialcomm); + if (serial_open(serial, SERIAL_RDWR) != SR_OK) + goto err_out; + + p = rdtech_um_probe(serial); + if (!p) { + sr_err("Failed to find a supported RDTech UM device."); + goto err_out_serial; + } + + devc = g_malloc0(sizeof(struct dev_context)); + sdi = g_malloc0(sizeof(struct sr_dev_inst)); + + sr_sw_limits_init(&devc->limits); + devc->profile = p; + + sdi->status = SR_ST_INACTIVE; + sdi->vendor = g_strdup("RDTech"); + sdi->model = g_strdup(p->model_name); + sdi->version = NULL; + sdi->inst_type = SR_INST_SERIAL; + sdi->conn = serial; + sdi->priv = devc; + + for (int i = 0; p->channels[i].name; i++) + sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, TRUE, + p->channels[i].name); + + devices = g_slist_append(devices, sdi); + serial_close(serial); + if (!devices) + sr_serial_dev_inst_free(serial); + + return std_scan_complete(di, devices); + +err_out_serial: + serial_close(serial); +err_out: + sr_serial_dev_inst_free(serial); + + return NULL; +} + +static GSList *scan(struct sr_dev_driver *di, GSList *options) +{ + struct sr_config *src; + const char *conn = NULL; + const char *serialcomm = RDTECH_UM_SERIALCOMM; + + for (GSList *l = options; l; l = l->next) { + src = l->data; + switch (src->key) { + case SR_CONF_CONN: + conn = g_variant_get_string(src->data, NULL); + break; + case SR_CONF_SERIALCOMM: + serialcomm = g_variant_get_string(src->data, NULL); + break; + } + } + if (!conn) + return NULL; + + return rdtech_um_scan(di, conn, serialcomm); +} + +static int config_set(uint32_t key, GVariant *data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + + (void)cg; + + devc = sdi->priv; + + return sr_sw_limits_config_set(&devc->limits, key, data); +} + +static int config_list(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); +} + +static int dev_acquisition_start(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc = sdi->priv; + struct sr_serial_dev_inst *serial = sdi->conn; + + sr_sw_limits_acquisition_start(&devc->limits); + std_session_send_df_header(sdi); + + serial_source_add(sdi->session, serial, G_IO_IN, 50, + rdtech_um_receive_data, (void *)sdi); + + return rdtech_um_poll(sdi); +} + +static struct sr_dev_driver rdtech_um_driver_info = { + .name = "rdtech-um", + .longname = "RDTech UMxx USB power meter", + .api_version = 1, + .init = std_init, + .cleanup = std_cleanup, + .scan = scan, + .dev_list = std_dev_list, + .dev_clear = std_dev_clear, + .config_get = NULL, + .config_set = config_set, + .config_list = config_list, + .dev_open = std_serial_dev_open, + .dev_close = std_serial_dev_close, + .dev_acquisition_start = dev_acquisition_start, + .dev_acquisition_stop = std_serial_dev_acquisition_stop, + .context = NULL, +}; +SR_REGISTER_DEV_DRIVER(rdtech_um_driver_info); diff --git a/src/hardware/rdtech-um/protocol.c b/src/hardware/rdtech-um/protocol.c new file mode 100644 index 00000000..25bad0df --- /dev/null +++ b/src/hardware/rdtech-um/protocol.c @@ -0,0 +1,235 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2018-2020 Andreas Sandberg + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include "libsigrok-internal.h" +#include "protocol.h" + +#define SERIAL_WRITE_TIMEOUT_MS 1 + +#define UM_POLL_LEN 130 +#define UM_POLL_PERIOD_MS 100 +#define UM_TIMEOUT_MS 1000 + +#define UM_CMD_POLL 0xf0 + +static const struct binary_analog_channel rdtech_default_channels[] = { + { "V", { 2, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "I", { 4, BVT_BE_UINT16, 0.001, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE }, + { "D+", { 96, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "D-", { 98, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "T", { 10, BVT_BE_UINT16, 1.0, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS }, + /* Threshold-based recording (mWh) */ + { "E", { 106, BVT_BE_UINT32, 0.001, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR }, + { NULL, }, +}; + +static const struct binary_analog_channel rdtech_um25c_channels[] = { + { "V", { 2, BVT_BE_UINT16, 0.001, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "I", { 4, BVT_BE_UINT16, 0.0001, }, 3, SR_MQ_CURRENT, SR_UNIT_AMPERE }, + { "D+", { 96, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "D-", { 98, BVT_BE_UINT16, 0.01, }, 2, SR_MQ_VOLTAGE, SR_UNIT_VOLT }, + { "T", { 10, BVT_BE_UINT16, 1.0, }, 0, SR_MQ_TEMPERATURE, SR_UNIT_CELSIUS }, + /* Threshold-based recording (mWh) */ + { "E", { 106, BVT_BE_UINT32, 0.001, }, 3, SR_MQ_ENERGY, SR_UNIT_WATT_HOUR }, + { NULL, }, +}; + +static int poll_csum_fff1(char buf[], int len) +{ + if (len != UM_POLL_LEN) + return 0; + else + return RB16(&buf[len - 2]) == 0xFFF1; +} + +static int poll_csum_um34c(char buf[], int len) +{ + static const int positions[] = { + 1, 3, 7, 9, 15, 17, 19, 23, 31, 39, 41, 45, 49, 53, + 55, 57, 59, 63, 67, 69, 73, 79, 83, 89, 97, 99, 109, + 111, 113, 119, 121, 127, + }; + unsigned int i; + uint8_t csum = 0; + + if (len != UM_POLL_LEN) + return 0; + + for (i = 0; i < ARRAY_SIZE(positions); i++) + csum ^= buf[positions[i]]; + + return csum == (uint8_t)buf[len - 1]; +} + +static const struct rdtech_um_profile um_profiles[] = { + { "UM24C", RDTECH_UM24C, rdtech_default_channels, &poll_csum_fff1, }, + { "UM25C", RDTECH_UM25C, rdtech_um25c_channels, &poll_csum_fff1, }, + { "UM34C", RDTECH_UM34C, rdtech_default_channels, &poll_csum_um34c, }, +}; + +static const struct rdtech_um_profile *find_profile(uint16_t id) +{ + unsigned int i; + for (i = 0; i < ARRAY_SIZE(um_profiles); i++) { + if (um_profiles[i].model_id == id) + return &um_profiles[i]; + } + return NULL; +} + +SR_PRIV const struct rdtech_um_profile *rdtech_um_probe(struct sr_serial_dev_inst *serial) +{ + const struct rdtech_um_profile *p; + static const uint8_t request = UM_CMD_POLL; + char buf[RDTECH_UM_BUFSIZE]; + int len; + + if (serial_write_blocking(serial, &request, sizeof(request), + SERIAL_WRITE_TIMEOUT_MS) < 0) { + sr_err("Unable to send probe request."); + return NULL; + } + + len = serial_read_blocking(serial, buf, UM_POLL_LEN, UM_TIMEOUT_MS); + if (len != UM_POLL_LEN) { + sr_err("Failed to read probe response."); + return NULL; + } + + p = find_profile(RB16(&buf[0])); + if (!p) { + sr_err("Unrecognized UM device (0x%.4" PRIx16 ")!", RB16(&buf[0])); + return NULL; + } + + if (!p->poll_csum(buf, len)) { + sr_err("Probe response contains illegal checksum or end marker.\n"); + return NULL; + } + + return p; +} + +SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc = sdi->priv; + struct sr_serial_dev_inst *serial = sdi->conn; + static const uint8_t request = UM_CMD_POLL; + + if (serial_write_blocking(serial, &request, sizeof(request), + SERIAL_WRITE_TIMEOUT_MS) < 0) { + sr_err("Unable to send poll request."); + return SR_ERR; + } + + devc->cmd_sent_at = g_get_monotonic_time() / 1000; + + return SR_OK; +} + +static void handle_poll_data(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc = sdi->priv; + int i; + GSList *ch; + + sr_spew("Received poll packet (len: %d).", devc->buflen); + if (devc->buflen != UM_POLL_LEN) { + sr_err("Unexpected poll packet length: %i", devc->buflen); + return; + } + + for (ch = sdi->channels, i = 0; ch; ch = g_slist_next(ch), i++) + bv_send_analog_channel(sdi, ch->data, + &devc->profile->channels[i], + devc->buf, devc->buflen); + + sr_sw_limits_update_samples_read(&devc->limits, 1); +} + +static void recv_poll_data(struct sr_dev_inst *sdi, struct sr_serial_dev_inst *serial) +{ + struct dev_context *devc = sdi->priv; + const struct rdtech_um_profile *p = devc->profile; + int len; + + /* Serial data arrived. */ + while (devc->buflen < UM_POLL_LEN) { + len = serial_read_nonblocking(serial, devc->buf + devc->buflen, 1); + if (len < 1) + return; + + devc->buflen++; + + /* Check if the poll model ID matches the profile. */ + if (devc->buflen == 2 && RB16(devc->buf) != p->model_id) { + sr_warn("Illegal model ID in poll response (0x%.4" PRIx16 ")," + " skipping 1 byte.", + RB16(devc->buf)); + devc->buflen--; + memmove(devc->buf, devc->buf + 1, devc->buflen); + } + } + + if (devc->buflen == UM_POLL_LEN && p->poll_csum(devc->buf, devc->buflen)) + handle_poll_data(sdi); + else + sr_warn("Skipping packet with illegal checksum / end marker."); + + devc->buflen = 0; +} + +SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + struct sr_serial_dev_inst *serial; + int64_t now, elapsed; + + (void)fd; + + if (!(sdi = cb_data)) + return TRUE; + + if (!(devc = sdi->priv)) + return TRUE; + + serial = sdi->conn; + if (revents == G_IO_IN) + recv_poll_data(sdi, serial); + + if (sr_sw_limits_check(&devc->limits)) { + sr_dev_acquisition_stop(sdi); + return TRUE; + } + + now = g_get_monotonic_time() / 1000; + elapsed = now - devc->cmd_sent_at; + + if (elapsed > UM_POLL_PERIOD_MS) + rdtech_um_poll(sdi); + + return TRUE; +} diff --git a/src/hardware/rdtech-um/protocol.h b/src/hardware/rdtech-um/protocol.h new file mode 100644 index 00000000..f344f1b9 --- /dev/null +++ b/src/hardware/rdtech-um/protocol.h @@ -0,0 +1,64 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2018-2020 Andreas Sandberg + * + * 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 . + */ + +#ifndef LIBSIGROK_HARDWARE_RDTECH_UM_PROTOCOL_H +#define LIBSIGROK_HARDWARE_RDTECH_UM_PROTOCOL_H + +#include +#include "libsigrok-internal.h" + +#define LOG_PREFIX "rdtech-um" + +#define RDTECH_UM_BUFSIZE 256 + +enum rdtech_um_model_id { + RDTECH_UM24C = 0x0963, + RDTECH_UM25C = 0x09c9, + RDTECH_UM34C = 0x0d4c, +}; + +enum rdtech_um_checksum { + RDTECH_CSUM_STATIC_FFF1, + RDTECH_CSUM_UM34C, +}; + +/* Supported device profiles */ +struct rdtech_um_profile { + const char *model_name; + enum rdtech_um_model_id model_id; + const struct binary_analog_channel *channels; + + /* Verify poll packet checksum; return 1 if OK, 0 otherwise. */ + int (*poll_csum)(char buf[], int len); +}; + +struct dev_context { + const struct rdtech_um_profile *profile; + struct sr_sw_limits limits; + + char buf[RDTECH_UM_BUFSIZE]; + int buflen; + int64_t cmd_sent_at; +}; + +SR_PRIV const struct rdtech_um_profile *rdtech_um_probe(struct sr_serial_dev_inst *serial); +SR_PRIV int rdtech_um_receive_data(int fd, int revents, void *cb_data); +SR_PRIV int rdtech_um_poll(const struct sr_dev_inst *sdi); + +#endif