From 69b05583955761d24c86cf1f46c1d1dba8c6176c Mon Sep 17 00:00:00 2001 From: James Churchill Date: Tue, 20 Feb 2018 11:42:56 +1000 Subject: [PATCH] rdtech-dps: New driver for RDTech DPS/DPH series PSUs. --- configure.ac | 2 +- src/hardware/rdtech-dps/api.c | 280 +++++++++++++++++++++++++---- src/hardware/rdtech-dps/protocol.c | 104 ++++++++++- src/hardware/rdtech-dps/protocol.h | 63 ++++++- 4 files changed, 403 insertions(+), 46 deletions(-) diff --git a/configure.ac b/configure.ac index 9590d800..d9f6f70e 100644 --- a/configure.ac +++ b/configure.ac @@ -262,7 +262,7 @@ SR_DRIVER([Norma DMM], [norma-dmm], [libserialport]) SR_DRIVER([OpenBench Logic Sniffer], [openbench-logic-sniffer], [libserialport]) SR_DRIVER([PCE PCE-322A], [pce-322a], [libserialport]) SR_DRIVER([Pipistrello-OLS], [pipistrello-ols], [libftdi]) -SR_DRIVER([RDTech DPS], [rdtech-dps]) +SR_DRIVER([RDTech DPSxxxx/DPHxxxx], [rdtech-dps], [libserialport]) SR_DRIVER([Rigol DS], [rigol-ds]) SR_DRIVER([Rohde&Schwarz SME-0x], [rohde-schwarz-sme-0x], [libserialport]) SR_DRIVER([Saleae Logic16], [saleae-logic16], [libusb]) diff --git a/src/hardware/rdtech-dps/api.c b/src/hardware/rdtech-dps/api.c index 18299754..f719e6c7 100644 --- a/src/hardware/rdtech-dps/api.c +++ b/src/hardware/rdtech-dps/api.c @@ -20,53 +20,226 @@ #include #include "protocol.h" +static const uint32_t scanopts[] = { + SR_CONF_CONN, + SR_CONF_SERIALCOMM, + SR_CONF_MODBUSADDR, +}; + +static const uint32_t drvopts[] = { + SR_CONF_POWER_SUPPLY, +}; + +static const uint32_t devopts[] = { + SR_CONF_CONTINUOUS, + SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, + SR_CONF_VOLTAGE | SR_CONF_GET, + SR_CONF_VOLTAGE_TARGET | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_CURRENT | SR_CONF_GET, + SR_CONF_CURRENT_LIMIT | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET, + SR_CONF_REGULATION | SR_CONF_GET, + SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE | SR_CONF_GET, + SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET, + SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE | SR_CONF_GET, + SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET, +}; + +/* Model ID, model name, max current, max voltage, max power */ +static const struct rdtech_dps_model supported_models[] = { + { 3005, "DPS3005", 3, 50, 160 }, + { 5005, "DPS5005", 5, 50, 250 }, + { 5205, "DPH5005", 5, 50, 250 }, + { 5015, "DPS5015", 15, 50, 750 }, + { 5020, "DPS5020", 20, 50, 1000 }, + { 8005, "DPS8005", 5, 80, 408 }, +}; + +static struct sr_dev_driver rdtech_dps_driver_info; + +static struct sr_dev_inst *probe_device(struct sr_modbus_dev_inst *modbus) +{ + const struct rdtech_dps_model *model = NULL; + struct dev_context *devc; + struct sr_dev_inst *sdi; + uint16_t id, version; + unsigned int i; + + int ret = rdtech_dps_get_model_version(modbus, &id, &version); + if (ret != SR_OK) + return NULL; + for (i = 0; i < ARRAY_SIZE(supported_models); i++) + if (id == supported_models[i].id) { + model = &supported_models[i]; + break; + } + if (model == NULL) { + sr_err("Unknown model: %d.", id); + return NULL; + } + + sdi = g_malloc0(sizeof(struct sr_dev_inst)); + sdi->status = SR_ST_INACTIVE; + sdi->vendor = g_strdup("RDTech"); + sdi->model = g_strdup(model->name); + sdi->version = g_strdup_printf("v%d", version); + sdi->conn = modbus; + sdi->driver = &rdtech_dps_driver_info; + sdi->inst_type = SR_INST_MODBUS; + + sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "V"); + sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "I"); + sr_channel_new(sdi, 0, SR_CHANNEL_ANALOG, TRUE, "P"); + + devc = g_malloc0(sizeof(struct dev_context)); + sr_sw_limits_init(&devc->limits); + devc->model = model; + devc->expecting_registers = 0; + + sdi->priv = devc; + + return sdi; +} + +static int config_compare(gconstpointer a, gconstpointer b) +{ + const struct sr_config *ac = a, *bc = b; + return ac->key != bc->key; +} + static GSList *scan(struct sr_dev_driver *di, GSList *options) { - struct drv_context *drvc; - GSList *devices; + struct sr_config default_serialcomm = { + .key = SR_CONF_SERIALCOMM, + .data = g_variant_new_string("9600/8n1"), + }; + struct sr_config default_modbusaddr = { + .key = SR_CONF_MODBUSADDR, + .data = g_variant_new_uint64(1), + }; + GSList *opts = options, *devices; - (void)options; + if (!g_slist_find_custom(options, &default_serialcomm, config_compare)) + opts = g_slist_prepend(opts, &default_serialcomm); + if (!g_slist_find_custom(options, &default_modbusaddr, config_compare)) + opts = g_slist_prepend(opts, &default_modbusaddr); - devices = NULL; - drvc = di->context; - drvc->instances = NULL; + devices = sr_modbus_scan(di->context, opts, probe_device); - /* TODO: scan for devices, either based on a SR_CONF_CONN option - * or on a USB scan. */ + while (opts != options) + opts = g_slist_delete_link(opts, opts); + g_variant_unref(default_serialcomm.data); + g_variant_unref(default_modbusaddr.data); return devices; } static int dev_open(struct sr_dev_inst *sdi) { - (void)sdi; + struct sr_modbus_dev_inst *modbus = sdi->conn; - /* TODO: get handle from sdi->conn and open it. */ + if (sr_modbus_open(modbus) < 0) + return SR_ERR; + + rdtech_dps_set_reg(modbus, REG_LOCK, 1); return SR_OK; } static int dev_close(struct sr_dev_inst *sdi) { - (void)sdi; + struct dev_context *devc; + struct sr_modbus_dev_inst *modbus; - /* TODO: get handle from sdi->conn and close it. */ + modbus = sdi->conn; - return SR_OK; + if (!modbus) + return SR_ERR_BUG; + + devc = sdi->priv; + + if (devc->expecting_registers) { + /* Wait for the last data that was requested from the device. */ + uint16_t registers[devc->expecting_registers]; + sr_modbus_read_holding_registers(modbus, -1, + devc->expecting_registers, registers); + } + + rdtech_dps_set_reg(modbus, REG_LOCK, 0); + + return sr_modbus_close(modbus); } static int config_get(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { + struct dev_context *devc; + struct sr_modbus_dev_inst *modbus; int ret; + uint16_t ivalue; - (void)sdi; - (void)data; (void)cg; + modbus = sdi->conn; + devc = sdi->priv; + ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_LIMIT_SAMPLES: + case SR_CONF_LIMIT_MSEC: + ret = sr_sw_limits_config_get(&devc->limits, key, data); + break; + case SR_CONF_ENABLED: + if ((ret = rdtech_dps_get_reg(modbus, REG_ENABLE, &ivalue)) == SR_OK) + *data = g_variant_new_boolean(ivalue); + break; + case SR_CONF_REGULATION: + if ((ret = rdtech_dps_get_reg(modbus, REG_CV_CC, &ivalue)) != SR_OK) + break; + if (ivalue == MODE_CC) + *data = g_variant_new_string("CC"); + else + *data = g_variant_new_string("CV"); + break; + case SR_CONF_VOLTAGE: + if ((ret = rdtech_dps_get_reg(modbus, REG_UOUT, &ivalue)) == SR_OK) + *data = g_variant_new_double((float)ivalue / 100.0f); + break; + case SR_CONF_VOLTAGE_TARGET: + if ((ret = rdtech_dps_get_reg(modbus, REG_USET, &ivalue)) == SR_OK) + *data = g_variant_new_double((float)ivalue / 100.0f); + break; + case SR_CONF_CURRENT: + if ((ret = rdtech_dps_get_reg(modbus, REG_IOUT, &ivalue)) == SR_OK) + *data = g_variant_new_double((float)ivalue / 100.0f); + break; + case SR_CONF_CURRENT_LIMIT: + if ((ret = rdtech_dps_get_reg(modbus, REG_ISET, &ivalue)) == SR_OK) + *data = g_variant_new_double((float)ivalue / 1000.0f); + break; + case SR_CONF_OVER_VOLTAGE_PROTECTION_ENABLED: + *data = g_variant_new_boolean(TRUE); + break; + case SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE: + if ((ret = rdtech_dps_get_reg(modbus, REG_PROTECT, &ivalue)) == SR_OK) + *data = g_variant_new_boolean(ivalue == STATE_OVP); + break; + case SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD: + if ((ret = rdtech_dps_get_reg(modbus, PRE_OVPSET, &ivalue)) == SR_OK) + *data = g_variant_new_double((float)ivalue / 100.0f); + break; + case SR_CONF_OVER_CURRENT_PROTECTION_ENABLED: + *data = g_variant_new_boolean(TRUE); + break; + case SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE: + if ((ret = rdtech_dps_get_reg(modbus, REG_PROTECT, &ivalue)) == SR_OK) + *data = g_variant_new_boolean(ivalue == STATE_OCP); + break; + case SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD: + if ((ret = rdtech_dps_get_reg(modbus, PRE_OCPSET, &ivalue)) == SR_OK) + *data = g_variant_new_double((float)ivalue / 1000.0f); + break; default: return SR_ERR_NA; } @@ -77,63 +250,93 @@ static int config_get(uint32_t key, GVariant **data, static int config_set(uint32_t key, GVariant *data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { - int ret; + struct dev_context *devc; + struct sr_modbus_dev_inst *modbus; - (void)sdi; - (void)data; (void)cg; - ret = SR_OK; + modbus = sdi->conn; + devc = sdi->priv; + switch (key) { - /* TODO */ + case SR_CONF_LIMIT_SAMPLES: + case SR_CONF_LIMIT_MSEC: + return sr_sw_limits_config_set(&devc->limits, key, data); + case SR_CONF_ENABLED: + return rdtech_dps_set_reg(modbus, REG_ENABLE, g_variant_get_boolean(data)); + case SR_CONF_VOLTAGE_TARGET: + return rdtech_dps_set_reg(modbus, REG_USET, g_variant_get_double(data) * 100); + case SR_CONF_CURRENT_LIMIT: + return rdtech_dps_set_reg(modbus, REG_ISET, g_variant_get_double(data) * 1000); + case SR_CONF_OVER_VOLTAGE_PROTECTION_THRESHOLD: + return rdtech_dps_set_reg(modbus, PRE_OVPSET, g_variant_get_double(data) * 100); + case SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD: + return rdtech_dps_set_reg(modbus, PRE_OCPSET, g_variant_get_double(data) * 1000); default: - ret = SR_ERR_NA; + return SR_ERR_NA; } - return ret; + return SR_OK; } static int config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { - int ret; + struct dev_context *devc; - (void)sdi; - (void)data; - (void)cg; + devc = (sdi) ? sdi->priv : NULL; - ret = SR_OK; switch (key) { - /* TODO */ + case SR_CONF_SCAN_OPTIONS: + case SR_CONF_DEVICE_OPTIONS: + return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); + case SR_CONF_VOLTAGE_TARGET: + *data = std_gvar_min_max_step(0.0, devc->model->max_voltage, 0.001); + break; + case SR_CONF_CURRENT_LIMIT: + *data = std_gvar_min_max_step(0.0, devc->model->max_current, 0.0001); + break; default: return SR_ERR_NA; } - return ret; + return SR_OK; } static int dev_acquisition_start(const struct sr_dev_inst *sdi) { - /* TODO: configure hardware, reset acquisition state, set up - * callbacks and send header packet. */ + struct dev_context *devc; + struct sr_modbus_dev_inst *modbus; + int ret; - (void)sdi; + modbus = sdi->conn; + devc = sdi->priv; - return SR_OK; + if ((ret = sr_modbus_source_add(sdi->session, modbus, G_IO_IN, 10, + rdtech_dps_receive_data, (void *)sdi)) != SR_OK) + return ret; + + sr_sw_limits_acquisition_start(&devc->limits); + std_session_send_df_header(sdi); + + return rdtech_dps_capture_start(sdi); } static int dev_acquisition_stop(struct sr_dev_inst *sdi) { - /* TODO: stop acquisition. */ + struct sr_modbus_dev_inst *modbus; - (void)sdi; + std_session_send_df_end(sdi); + + modbus = sdi->conn; + sr_modbus_source_remove(sdi->session, modbus); return SR_OK; } -SR_PRIV struct sr_dev_driver rdtech_dps_driver_info = { +static struct sr_dev_driver rdtech_dps_driver_info = { .name = "rdtech-dps", - .longname = "RDTech DPS", + .longname = "RDTech DPS/DPH series power supply", .api_version = 1, .init = std_init, .cleanup = std_cleanup, @@ -149,5 +352,4 @@ SR_PRIV struct sr_dev_driver rdtech_dps_driver_info = { .dev_acquisition_stop = dev_acquisition_stop, .context = NULL, }; - SR_REGISTER_DEV_DRIVER(rdtech_dps_driver_info); diff --git a/src/hardware/rdtech-dps/protocol.c b/src/hardware/rdtech-dps/protocol.c index 05b0e06b..4df1a701 100644 --- a/src/hardware/rdtech-dps/protocol.c +++ b/src/hardware/rdtech-dps/protocol.c @@ -20,22 +20,116 @@ #include #include "protocol.h" +SR_PRIV int rdtech_dps_get_reg(struct sr_modbus_dev_inst *modbus, + uint16_t address, uint16_t *value) +{ + uint16_t registers[1]; + int ret = sr_modbus_read_holding_registers(modbus, address, 1, registers); + *value = RB16(registers + 0); + return ret; +} + +SR_PRIV int rdtech_dps_set_reg(struct sr_modbus_dev_inst *modbus, + uint16_t address, uint16_t value) +{ + uint16_t registers[1]; + WB16(registers, value); + return sr_modbus_write_multiple_registers(modbus, address, 1, registers); +} + +SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus, + uint16_t *model, uint16_t *version) +{ + uint16_t registers[2]; + int ret; + ret = sr_modbus_read_holding_registers(modbus, REG_MODEL, 2, registers); + if (ret == SR_OK) { + *model = RB16(registers + 0); + *version = RB16(registers + 1); + sr_info("RDTech PSU model: %d version: %d", *model, *version); + } + return ret; +} + +static void send_value(const struct sr_dev_inst *sdi, struct sr_channel *ch, + float value, enum sr_mq mq, enum sr_unit unit, int digits) +{ + struct sr_datafeed_packet packet; + struct sr_datafeed_analog analog; + struct sr_analog_encoding encoding; + struct sr_analog_meaning meaning; + struct sr_analog_spec spec; + + sr_analog_init(&analog, &encoding, &meaning, &spec, digits); + analog.meaning->channels = g_slist_append(NULL, ch); + analog.num_samples = 1; + analog.data = &value; + analog.meaning->mq = mq; + analog.meaning->unit = unit; + analog.meaning->mqflags = SR_MQFLAG_DC; + + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + sr_session_send(sdi, &packet); + g_slist_free(analog.meaning->channels); +} + +SR_PRIV int rdtech_dps_capture_start(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_modbus_dev_inst *modbus; + int ret; + + modbus = sdi->conn; + devc = sdi->priv; + + if ((ret = sr_modbus_read_holding_registers(modbus, REG_UOUT, 3, NULL)) == SR_OK) + devc->expecting_registers = 2; + return ret; +} + SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data) { - const struct sr_dev_inst *sdi; + struct sr_dev_inst *sdi; struct dev_context *devc; + struct sr_modbus_dev_inst *modbus; + struct sr_datafeed_packet packet; + uint16_t registers[3]; (void)fd; + (void)revents; if (!(sdi = cb_data)) return TRUE; - if (!(devc = sdi->priv)) - return TRUE; + modbus = sdi->conn; + devc = sdi->priv; - if (revents == G_IO_IN) { - /* TODO */ + devc->expecting_registers = 0; + if (sr_modbus_read_holding_registers(modbus, -1, 3, registers) == SR_OK) { + packet.type = SR_DF_FRAME_BEGIN; + sr_session_send(sdi, &packet); + + send_value(sdi, sdi->channels->data, + RB16(registers + 0) / 100.0f, + SR_MQ_VOLTAGE, SR_UNIT_VOLT, 3); + send_value(sdi, sdi->channels->next->data, + RB16(registers + 1) / 1000.0f, + SR_MQ_CURRENT, SR_UNIT_AMPERE, 4); + send_value(sdi, sdi->channels->next->next->data, + RB16(registers + 2) / 100.0f, + SR_MQ_POWER, SR_UNIT_WATT, 3); + + packet.type = SR_DF_FRAME_END; + sr_session_send(sdi, &packet); + sr_sw_limits_update_samples_read(&devc->limits, 1); } + if (sr_sw_limits_check(&devc->limits)) { + sr_dev_acquisition_stop(sdi); + return TRUE; + } + + rdtech_dps_capture_start(sdi); return TRUE; } diff --git a/src/hardware/rdtech-dps/protocol.h b/src/hardware/rdtech-dps/protocol.h index a5747fd8..3224e1b5 100644 --- a/src/hardware/rdtech-dps/protocol.h +++ b/src/hardware/rdtech-dps/protocol.h @@ -27,9 +27,70 @@ #define LOG_PREFIX "rdtech-dps" -struct dev_context { +struct rdtech_dps_model { + unsigned int id; + const char *name; + unsigned int max_current; + unsigned int max_voltage; + unsigned int max_power; }; +struct dev_context { + const struct rdtech_dps_model *model; + struct sr_sw_limits limits; + int expecting_registers; +}; + +enum rdtech_dps_register { + REG_USET = 0x00, /* Mirror of 0x50 */ + REG_ISET = 0x01, /* Mirror of 0x51 */ + REG_UOUT = 0x02, + REG_IOUT = 0x03, + REG_POWER = 0x04, + REG_UIN = 0x05, + REG_LOCK = 0x06, + REG_PROTECT = 0x07, + REG_CV_CC = 0x08, + REG_ENABLE = 0x09, + REG_BACKLIGHT = 0x0A, /* Mirror of 0x55 */ + REG_MODEL = 0x0B, + REG_VERSION = 0x0C, + + REG_PRESET = 0x23, /* Loads a preset into preset 0. */ + +/* + * Add (preset * 0x10) to each of the following, for preset 1-9. + * Preset 0 regs below are the active output settings. + */ + PRE_USET = 0x50, + PRE_ISET = 0x51, + PRE_OVPSET = 0x52, + PRE_OCPSET = 0x53, + PRE_OPPSET = 0x54, + PRE_BACKLIGHT = 0x55, + PRE_DISABLE = 0x56, /* Disable output if 0 is copied here from a preset (1 is no change). */ + PRE_BOOT = 0x57, /* Enable output at boot if 1. */ +}; + +enum rdtech_dps_state { + STATE_NORMAL = 0, + STATE_OVP = 1, + STATE_OCP = 2, + STATE_OPP = 3, +}; + +enum rdtech_dps_mode { + MODE_CV = 0, + MODE_CC = 1, +}; + +SR_PRIV int rdtech_dps_get_reg(struct sr_modbus_dev_inst *modbus, uint16_t address, uint16_t *value); +SR_PRIV int rdtech_dps_set_reg(struct sr_modbus_dev_inst *modbus, uint16_t address, uint16_t value); + +SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus, + uint16_t *model, uint16_t *version); + +SR_PRIV int rdtech_dps_capture_start(const struct sr_dev_inst *sdi); SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data); #endif