From: Mikael Djurfeldt Date: Mon, 16 Jan 2023 22:43:44 +0000 (+0100) Subject: rdtech-dps: add support for RD6006P, RD6012P and RD6024 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=02a4f485de76;p=libsigrok.git rdtech-dps: add support for RD6006P, RD6012P and RD6024 RD6012P has two current ranges, so support for getting/setting/listing range is added. This also means that the model table is reorganized to support multiple ranges. In order for the range setting and the current multiplier to be up to date, the function rdtech_dps_update_range is called when required. This function reads the range register of the device. --- diff --git a/src/hardware/rdtech-dps/api.c b/src/hardware/rdtech-dps/api.c index 1c67ea49..db9601f1 100644 --- a/src/hardware/rdtech-dps/api.c +++ b/src/hardware/rdtech-dps/api.c @@ -21,7 +21,6 @@ #include -#include #include #include "protocol.h" @@ -52,18 +51,94 @@ static const uint32_t devopts[] = { SR_CONF_OVER_CURRENT_PROTECTION_THRESHOLD | SR_CONF_GET | SR_CONF_SET, }; -/* Model ID, model name, max current/voltage/power, current/voltage digits. */ +static const uint32_t devopts_w_range[] = { + 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, + SR_CONF_RANGE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, +}; + +/* range name, max current/voltage/power, current/voltage digits */ +static struct rdtech_dps_range ranges_dps3005[] = { + { "5A", 5, 30, 160, 3, 2 } +}; + +static struct rdtech_dps_range ranges_dps5005[] = { + { "5A", 5, 50, 250, 3, 2 } +}; + +static struct rdtech_dps_range ranges_dps5015[] = { + { "15A", 15, 50, 750, 2, 2 } +}; + +static struct rdtech_dps_range ranges_dps5020[] = { + { "20A", 20, 50, 1000, 2, 2 } +}; + +static struct rdtech_dps_range ranges_dps8005[] = { + { "5A", 5, 80, 408, 3, 2 } +}; + +static struct rdtech_dps_range ranges_rd6006[] = { + { "6A", 6, 60, 360, 3, 2 } +}; + +static struct rdtech_dps_range ranges_rd6006p[] = { + { "6A", 6, 60, 360, 4, 3 } +}; + +static struct rdtech_dps_range ranges_rd6012[] = { + { "12A", 12, 60, 720, 2, 2 } +}; + +/* + * Current digits for RD6012P is 4 for the 6A range (RTU reg 20 = 0) and + * 3 for the 12A range (RTU reg 20 = 1) + */ +static struct rdtech_dps_range ranges_rd6012p[] = { + { "6A", 6, 60, 360, 4, 3 }, + { "12A", 12, 60, 720, 3, 3 } +}; + +static struct rdtech_dps_range ranges_rd6018[] = { + { "18A", 18, 60, 1080, 2, 2 } +}; + +static struct rdtech_dps_range ranges_rd6024[] = { + { "24A", 24, 60, 1440, 2, 2 } +}; + +/* model ID, model name, range */ static const struct rdtech_dps_model supported_models[] = { - { MODEL_DPS, 3005, "DPS3005", 5, 30, 160, 3, 2 }, - { MODEL_DPS, 5005, "DPS5005", 5, 50, 250, 3, 2 }, - { MODEL_DPS, 5205, "DPH5005", 5, 50, 250, 3, 2 }, - { MODEL_DPS, 5015, "DPS5015", 15, 50, 750, 2, 2 }, - { MODEL_DPS, 5020, "DPS5020", 20, 50, 1000, 2, 2 }, - { MODEL_DPS, 8005, "DPS8005", 5, 80, 408, 3, 2 }, - /* All RD specs taken from the 2020.12.2 instruction manual. */ - { MODEL_RD , 6006, "RD6006" , 6, 60, 360, 3, 2 }, - { MODEL_RD , 6012, "RD6012" , 12, 60, 720, 2, 2 }, - { MODEL_RD , 6018, "RD6018" , 18, 60, 1080, 2, 2 }, + { MODEL_DPS, 3005, "DPS3005", ARRAY_AND_SIZE(ranges_dps3005) }, + { MODEL_DPS, 5005, "DPS5005", ARRAY_AND_SIZE(ranges_dps5005) }, + { MODEL_DPS, 5205, "DPH5005", ARRAY_AND_SIZE(ranges_dps5005) }, + { MODEL_DPS, 5015, "DPS5015", ARRAY_AND_SIZE(ranges_dps5015) }, + { MODEL_DPS, 5020, "DPS5020", ARRAY_AND_SIZE(ranges_dps5020) }, + { MODEL_DPS, 8005, "DPS8005", ARRAY_AND_SIZE(ranges_dps8005) }, + /* + * Specs for models RD60nn taken from the 2020.12.2 instruction manual, + * specs for RD6006P from the 2021.2.26 (english) manual, + * specs for RD6012P from the 2021.10.26 (english) manual, + * and specs for RD6024P from the 2021.1.7 (english) manual. + */ + { MODEL_RD, 60061, "RD6006" , ARRAY_AND_SIZE(ranges_rd6006) }, + { MODEL_RD, 60062, "RD6006" , ARRAY_AND_SIZE(ranges_rd6006) }, + { MODEL_RD, 60065, "RD6006P", ARRAY_AND_SIZE(ranges_rd6006p) }, + { MODEL_RD, 60121, "RD6012" , ARRAY_AND_SIZE(ranges_rd6012) }, + { MODEL_RD, 60125, "RD6012P", ARRAY_AND_SIZE(ranges_rd6012p) }, + { MODEL_RD, 60181, "RD6018" , ARRAY_AND_SIZE(ranges_rd6018) }, + { MODEL_RD, 60241, "RD6024" , ARRAY_AND_SIZE(ranges_rd6024) }, }; static struct sr_dev_driver rdtech_dps_driver_info; @@ -139,10 +214,10 @@ static struct sr_dev_inst *probe_device(struct sr_modbus_dev_inst *modbus, devc = g_malloc0(sizeof(*devc)); sr_sw_limits_init(&devc->limits); devc->model = model; - devc->current_multiplier = pow(10.0, model->current_digits); - devc->voltage_multiplier = pow(10.0, model->voltage_digits); - sdi->priv = devc; + ret = rdtech_dps_update_range(sdi); + if (ret != SR_OK) + return NULL; return sdi; } @@ -375,6 +450,15 @@ static int config_get(uint32_t key, GVariant **data, return SR_ERR_DATA; *data = g_variant_new_double(state.ocp_threshold); break; + case SR_CONF_RANGE: + ret = rdtech_dps_get_state(sdi, &state, ST_CTX_CONFIG); + if (ret != SR_OK) + return ret; + if (!(state.mask & STATE_RANGE)) + return SR_ERR_DATA; + *data = g_variant_new_string( + devc->model->ranges[state.range].range_str); + break; default: return SR_ERR_NA; } @@ -387,6 +471,8 @@ static int config_set(uint32_t key, GVariant *data, { struct dev_context *devc; struct rdtech_dps_state state; + const char *range_str; + size_t i; (void)cg; @@ -417,6 +503,17 @@ static int config_set(uint32_t key, GVariant *data, state.ocp_threshold = g_variant_get_double(data); state.mask |= STATE_OCP_THRESHOLD; return rdtech_dps_set_state(sdi, &state); + case SR_CONF_RANGE: + range_str = g_variant_get_string(data, NULL); + for (i = 0; i < devc->model->n_ranges; ++i) { + if (g_strcmp0(devc->model->ranges[i].range_str, range_str) + == 0) { + state.range = i; + state.mask |= STATE_RANGE; + return rdtech_dps_set_state(sdi, &state); + } + } + return SR_ERR_NA; default: return SR_ERR_NA; } @@ -428,21 +525,40 @@ static int config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) { struct dev_context *devc; + struct rdtech_dps_range *range; + GVariantBuilder gvb; + size_t i; devc = (sdi) ? sdi->priv : NULL; switch (key) { case SR_CONF_SCAN_OPTIONS: case SR_CONF_DEVICE_OPTIONS: - return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); + if (devc->model->n_ranges > 1) + return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, + drvopts, devopts_w_range); + else + 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, + rdtech_dps_update_range(sdi); + range = &devc->model->ranges[devc->curr_range]; + *data = std_gvar_min_max_step(0.0, range->max_voltage, 1 / devc->voltage_multiplier); break; case SR_CONF_CURRENT_LIMIT: - *data = std_gvar_min_max_step(0.0, devc->model->max_current, + rdtech_dps_update_range(sdi); + range = &devc->model->ranges[devc->curr_range]; + *data = std_gvar_min_max_step(0.0, range->max_current, 1 / devc->current_multiplier); break; + case SR_CONF_RANGE: + g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY); + for (i = 0; i < devc->model->n_ranges; ++i) + g_variant_builder_add(&gvb, "s", + devc->model->ranges[i].range_str); + *data = g_variant_builder_end(&gvb); + break; default: return SR_ERR_NA; } diff --git a/src/hardware/rdtech-dps/protocol.c b/src/hardware/rdtech-dps/protocol.c index 83090d86..08623325 100644 --- a/src/hardware/rdtech-dps/protocol.c +++ b/src/hardware/rdtech-dps/protocol.c @@ -21,10 +21,12 @@ #include +#include #include #include "protocol.h" +/* These are the Modbus RTU registers for the family of rdtech-dps devices. */ enum rdtech_dps_register { REG_DPS_USET = 0x00, /* Mirror of 0x50 */ REG_DPS_ISET = 0x01, /* Mirror of 0x51 */ @@ -69,6 +71,10 @@ enum rdtech_dps_regulation_mode { MODE_CC = 1, }; +/* + * Some registers are specific to a certain device. For example, + * REG_RD_RANGE is specific to RD6012P. + */ enum rdtech_rd_register { REG_RD_MODEL = 0, /* u16 */ REG_RD_SERIAL = 1, /* u32 */ @@ -85,6 +91,8 @@ enum rdtech_rd_register { REG_RD_PROTECT = 16, /* u16 */ REG_RD_REGULATION = 17, /* u16 */ REG_RD_ENABLE = 18, /* u16 */ + REG_RD_PRESET = 19, /* u16 */ + REG_RD_RANGE = 20, /* u16 */ /* * Battery at 32 == 0x20 pp: * Mode, voltage, temperature, capacity, energy. @@ -108,12 +116,12 @@ static int rdtech_dps_read_holding_registers(struct sr_modbus_dev_inst *modbus, int ret; retries = 3; - while (retries--) { + do { ret = sr_modbus_read_holding_registers(modbus, address, nb_registers, registers); if (ret == SR_OK) return ret; - } + } while (--retries); return ret; } @@ -200,7 +208,7 @@ SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus, if (ret != SR_OK) return ret; rdptr = (void *)registers; - *model = read_u16be_inc(&rdptr) / 10; + *model = read_u16be_inc(&rdptr); *serno = read_u32be_inc(&rdptr); *version = read_u16be_inc(&rdptr); sr_info("RDTech RD model: %u version: %u, serno %u", @@ -213,6 +221,48 @@ SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus, /* UNREACH */ } +SR_PRIV void rdtech_dps_update_multipliers(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct rdtech_dps_range *range; + + devc = sdi->priv; + range = &devc->model->ranges[devc->curr_range]; + devc->current_multiplier = pow(10.0, range->current_digits); + devc->voltage_multiplier = pow(10.0, range->voltage_digits); +} + +/* + * Determine range of connected device. Don't do anything once + * acquisition has started (since the range will then be tracked). + */ +SR_PRIV int rdtech_dps_update_range(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + uint16_t range; + int ret; + + devc = sdi->priv; + + /* + * Only update range if there are multiple ranges and data + * acquisition hasn't started. + */ + if (devc->model->n_ranges == 1 || devc->acquisition_started) + return SR_OK; + if (devc->model->model_type != MODEL_RD) + return SR_ERR; + + ret = rdtech_dps_read_holding_registers(sdi->conn, + REG_RD_RANGE, 1, &range); + if (ret != SR_OK) + return ret; + devc->curr_range = range ? 1 : 0; + rdtech_dps_update_multipliers(sdi); + + return SR_OK; +} + /* Send a measured value to the session feed. */ static int send_value(const struct sr_dev_inst *sdi, struct sr_channel *ch, float value, @@ -259,13 +309,14 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, struct dev_context *devc; struct sr_modbus_dev_inst *modbus; gboolean get_config, get_init_state, get_curr_meas; - uint16_t registers[12]; + uint16_t registers[14]; int ret; const uint8_t *rdptr; uint16_t uset_raw, iset_raw, uout_raw, iout_raw, power_raw; uint16_t reg_val, reg_state, out_state, ovpset_raw, ocpset_raw; gboolean is_lock, is_out_enabled, is_reg_cc; gboolean uses_ovp, uses_ocp; + uint16_t range; float volt_target, curr_limit; float ovp_threshold, ocp_threshold; float curr_voltage, curr_current, curr_power; @@ -308,6 +359,13 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, (void)get_init_state; (void)get_curr_meas; + /* + * The model RD6012P has two voltage/current ranges. We set a + * default value here such that the compiler doesn't generate + * an uninitialized variable warning. + */ + range = 0; + switch (devc->model->model_type) { case MODEL_DPS: /* @@ -319,8 +377,8 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, * a hardware specific device driver ... */ g_mutex_lock(&devc->rw_mutex); - ret = rdtech_dps_read_holding_registers(modbus, - REG_DPS_USET, 10, registers); + ret = rdtech_dps_read_holding_registers(modbus, REG_DPS_USET, + REG_DPS_ENABLE - REG_DPS_USET + 1, registers); g_mutex_unlock(&devc->rw_mutex); if (ret != SR_OK) return ret; @@ -369,7 +427,11 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, /* Retrieve a set of adjacent registers. */ g_mutex_lock(&devc->rw_mutex); ret = rdtech_dps_read_holding_registers(modbus, - REG_RD_VOLT_TGT, 11, registers); + REG_RD_VOLT_TGT, + devc->model->n_ranges > 1 + ? REG_RD_RANGE - REG_RD_VOLT_TGT + 1 + : REG_RD_ENABLE - REG_RD_VOLT_TGT + 1, + registers); g_mutex_unlock(&devc->rw_mutex); if (ret != SR_OK) return ret; @@ -396,6 +458,10 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, is_reg_cc = reg_state == MODE_CC; out_state = read_u16be_inc(&rdptr); /* ENABLE */ is_out_enabled = out_state != 0; + if (devc->model->n_ranges > 1) { + rdptr += sizeof (uint16_t); /* PRESET */ + range = read_u16be_inc(&rdptr) ? 1 : 0; /* RANGE */ + } /* Retrieve a set of adjacent registers. */ g_mutex_lock(&devc->rw_mutex); @@ -456,6 +522,10 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi, state->mask |= STATE_CURRENT; state->power = curr_power; state->mask |= STATE_POWER; + if (devc->model->n_ranges > 1) { + state->range = range; + state->mask |= STATE_RANGE; + } return SR_OK; } @@ -575,6 +645,34 @@ SR_PRIV int rdtech_dps_set_state(const struct sr_dev_inst *sdi, return SR_ERR_ARG; } } + if (state->mask & STATE_RANGE) { + reg_value = state->range; + switch (devc->model->model_type) { + case MODEL_DPS: + if (reg_value > 0) + return SR_ERR_ARG; + break; + case MODEL_RD: + if (devc->model->n_ranges == 1) + /* No need to set. */ + return SR_OK; + ret = rdtech_rd_set_reg(sdi, REG_RD_RANGE, reg_value); + if (ret != SR_OK) + return ret; + if (!devc->acquisition_started) { + devc->curr_range = reg_value ? 1 : 0; + rdtech_dps_update_multipliers(sdi); + } + /* + * We rely on the data acquisition to update + * devc->curr_range. If we do it here, there + * will be no range meta package. + */ + break; + default: + return SR_ERR_ARG; + } + } return SR_OK; } @@ -589,6 +687,7 @@ SR_PRIV int rdtech_dps_seed_receive(const struct sr_dev_inst *sdi) if (!sdi || !sdi->priv) return SR_ERR_ARG; devc = sdi->priv; + devc->acquisition_started = TRUE; ret = rdtech_dps_get_state(sdi, &state, ST_CTX_PRE_ACQ); if (ret != SR_OK) @@ -602,6 +701,10 @@ SR_PRIV int rdtech_dps_seed_receive(const struct sr_dev_inst *sdi) devc->curr_cc_state = state.regulation_cc; if (state.mask & STATE_OUTPUT_ENABLED) devc->curr_out_state = state.output_enabled; + if (state.mask & STATE_RANGE) { + devc->curr_range = state.range; + rdtech_dps_update_multipliers(sdi); + } return SR_OK; } @@ -635,11 +738,11 @@ SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data) ch = g_slist_nth_data(sdi->channels, 0); send_value(sdi, ch, state.voltage, SR_MQ_VOLTAGE, SR_MQFLAG_DC, SR_UNIT_VOLT, - devc->model->voltage_digits); + devc->model->ranges[devc->curr_range].voltage_digits); ch = g_slist_nth_data(sdi->channels, 1); send_value(sdi, ch, state.current, SR_MQ_CURRENT, SR_MQFLAG_DC, SR_UNIT_AMPERE, - devc->model->current_digits); + devc->model->ranges[devc->curr_range].current_digits); ch = g_slist_nth_data(sdi->channels, 2); send_value(sdi, ch, state.power, SR_MQ_POWER, 0, SR_UNIT_WATT, 2); @@ -669,6 +772,13 @@ SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data) g_variant_new_boolean(state.output_enabled)); devc->curr_out_state = state.output_enabled; } + if (devc->curr_range != state.range) { + (void)sr_session_send_meta(sdi, SR_CONF_RANGE, + g_variant_new_string( + devc->model->ranges[state.range].range_str)); + devc->curr_range = state.range; + rdtech_dps_update_multipliers(sdi); + } /* Check optional acquisition limits. */ sr_sw_limits_update_samples_read(&devc->limits, 1); diff --git a/src/hardware/rdtech-dps/protocol.h b/src/hardware/rdtech-dps/protocol.h index 456e2041..72b7be7c 100644 --- a/src/hardware/rdtech-dps/protocol.h +++ b/src/hardware/rdtech-dps/protocol.h @@ -38,10 +38,8 @@ enum rdtech_dps_model_type { MODEL_RD, }; -struct rdtech_dps_model { - enum rdtech_dps_model_type model_type; - unsigned int id; - const char *name; +struct rdtech_dps_range { + const char *range_str; unsigned int max_current; unsigned int max_voltage; unsigned int max_power; @@ -49,6 +47,14 @@ struct rdtech_dps_model { unsigned int voltage_digits; }; +struct rdtech_dps_model { + enum rdtech_dps_model_type model_type; + unsigned int id; + const char *name; + struct rdtech_dps_range *ranges; + unsigned int n_ranges; +}; + struct dev_context { const struct rdtech_dps_model *model; double current_multiplier; @@ -59,6 +65,8 @@ struct dev_context { gboolean curr_ocp_state; gboolean curr_cc_state; gboolean curr_out_state; + unsigned int curr_range; + gboolean acquisition_started; }; /* Container to get and set parameter values. */ @@ -77,6 +85,7 @@ struct rdtech_dps_state { STATE_VOLTAGE = 1 << 10, STATE_CURRENT = 1 << 11, STATE_POWER = 1 << 12, + STATE_RANGE = 1 << 13, } mask; gboolean lock; gboolean output_enabled, regulation_cc; @@ -84,6 +93,7 @@ struct rdtech_dps_state { float voltage_target, current_limit; float ovp_threshold, ocp_threshold; float voltage, current, power; + unsigned int range; }; enum rdtech_dps_state_context { @@ -100,6 +110,8 @@ SR_PRIV int rdtech_dps_set_state(const struct sr_dev_inst *sdi, SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus, enum rdtech_dps_model_type model_type, uint16_t *model, uint16_t *version, uint32_t *serno); +SR_PRIV void rdtech_dps_update_multipliers(const struct sr_dev_inst *sdi); +SR_PRIV int rdtech_dps_update_range(const struct sr_dev_inst *sdi); SR_PRIV int rdtech_dps_seed_receive(const struct sr_dev_inst *sdi); SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data);