rdtech-dps: introduce support for RD6006 and other Riden RD models
authorGerhard Sittig <gerhard.sittig@gmx.net>
Sun, 25 Apr 2021 07:03:22 +0000 (09:03 +0200)
committerGerhard Sittig <gerhard.sittig@gmx.net>
Sun, 25 Apr 2021 10:10:26 +0000 (12:10 +0200)
The RD devices differ from DPS devices in their default communication
bitrate (115200), register content endianess (BE), and register set
layout (addresses for registers). In either case 16bit registers get
accessed by means of Modbus communication over a serial channel. The
interpretation of the registers' values mostly is the same as for DPS
devices once a register with the same meaning got identified. Device
identification includes a 32bit serial number which DPS devices appear
to not provide.

All other product features are the same, and map to identical sigrok
mechanisms. That's why re-using the rdtech-dps driver to add rdtech-rd
support is considered desirable. This implementation shares all code
"above" the raw register addressing and raw value interpretation. All
logical processing, configuration interface, state tracking and data
submission to the session feed, are shared among the different device
types.

Declare support for the RD6006, RD6012, and RD6018 models. Their specs
were taken from the 2020.12.2 instruction manual. The driver was tested
with an RD6006 device (firmware version 1.28). Unfortunately automatic
device detection is not possible or will not be reliable, which is why
users need to specify the respective model by picking one of the two
drivers. Within a driver variant the device identification and use of
the device are automatically dealt with.

src/hardware/rdtech-dps/api.c
src/hardware/rdtech-dps/protocol.c
src/hardware/rdtech-dps/protocol.h

index dda75896140cbd0efaed57d9b589934ed43bbd51..568ba575f4c5b6701a841b16732388f6425ea301 100644 (file)
@@ -54,47 +54,82 @@ static const uint32_t devopts[] = {
 
 /* Model ID, model name, max current/voltage/power, current/voltage digits. */
 static const struct rdtech_dps_model supported_models[] = {
-       { 3005, "DPS3005",  5, 30,  160, 3, 2 },
-       { 5005, "DPS5005",  5, 50,  250, 3, 2 },
-       { 5205, "DPH5005",  5, 50,  250, 3, 2 },
-       { 5015, "DPS5015", 15, 50,  750, 2, 2 },
-       { 5020, "DPS5020", 20, 50, 1000, 2, 2 },
-       { 8005, "DPS8005",  5, 80,  408, 3, 2 },
+       { 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 },
 };
 
 static struct sr_dev_driver rdtech_dps_driver_info;
+static struct sr_dev_driver rdtech_rd_driver_info;
 
-static struct sr_dev_inst *probe_device(struct sr_modbus_dev_inst *modbus)
+static struct sr_dev_inst *probe_device(struct sr_modbus_dev_inst *modbus,
+       enum rdtech_dps_model_type model_type)
 {
+       static const char *type_prefix[] = {
+               [MODEL_DPS] = "DPS",
+               [MODEL_RD]  = "RD",
+       };
+
        uint16_t id, version;
+       uint32_t serno;
        int ret;
-       const struct rdtech_dps_model *model;
+       const struct rdtech_dps_model *model, *supported;
        size_t i;
        struct sr_dev_inst *sdi;
        struct dev_context *devc;
 
-       ret = rdtech_dps_get_model_version(modbus, &id, &version);
+       ret = rdtech_dps_get_model_version(modbus,
+               model_type, &id, &version, &serno);
+       sr_dbg("probe: ret %d, type %s, model %u, vers %u, snr %u.",
+               ret, type_prefix[model_type], id, version, serno);
        if (ret != SR_OK)
                return NULL;
        model = NULL;
        for (i = 0; i < ARRAY_SIZE(supported_models); i++) {
-               if (id == supported_models[i].id) {
-                       model = &supported_models[i];
-                       break;
-               }
+               supported = &supported_models[i];
+               if (model_type != supported->model_type)
+                       continue;
+               if (id != supported->id)
+                       continue;
+               model = supported;
+               break;
        }
        if (!model) {
-               sr_err("Unknown model: %u.", id);
+               sr_err("Unknown model: %s%u.", type_prefix[model_type], id);
                return NULL;
        }
 
        sdi = g_malloc0(sizeof(*sdi));
        sdi->status = SR_ST_INACTIVE;
        sdi->vendor = g_strdup("RDTech");
-       sdi->model = g_strdup(model->name);
-       sdi->version = g_strdup_printf("v%u", version);
+       switch (model_type) {
+       case MODEL_DPS:
+               sdi->model = g_strdup(model->name);
+               sdi->version = g_strdup_printf("v%u", version);
+               sdi->driver = &rdtech_dps_driver_info;
+               break;
+       case MODEL_RD:
+               sdi->model = g_strdup(model->name);
+               sdi->version = g_strdup_printf("v%u.%u",
+                       version / 100, version % 100);
+               if (serno)
+                       sdi->serial_num = g_strdup_printf("%u", serno);
+               sdi->driver = &rdtech_rd_driver_info;
+               break;
+       default:
+               sr_err("Programming error, unhandled DPS/DPH/RD device type.");
+               g_free(sdi->vendor);
+               g_free(sdi);
+               return NULL;
+       }
        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");
@@ -112,23 +147,55 @@ static struct sr_dev_inst *probe_device(struct sr_modbus_dev_inst *modbus)
        return sdi;
 }
 
+static struct sr_dev_inst *probe_device_dps(struct sr_modbus_dev_inst *modbus)
+{
+       return probe_device(modbus, MODEL_DPS);
+}
+
+static struct sr_dev_inst *probe_device_rd(struct sr_modbus_dev_inst *modbus)
+{
+       return probe_device(modbus, MODEL_RD);
+}
+
 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)
+static GSList *scan(struct sr_dev_driver *di, GSList *options,
+       enum rdtech_dps_model_type model_type)
 {
+       static const char *default_serialcomm_dps = "9600/8n1";
+       static const char *default_serialcomm_rd = "115200/8n1";
+
        struct sr_config default_serialcomm = {
                .key = SR_CONF_SERIALCOMM,
-               .data = g_variant_new_string("9600/8n1"),
+               .data = NULL,
        };
        struct sr_config default_modbusaddr = {
                .key = SR_CONF_MODBUSADDR,
                .data = g_variant_new_uint64(1),
        };
        GSList *opts, *devices;
+       const char *serialcomm;
+       struct sr_dev_inst *(*probe_func)(struct sr_modbus_dev_inst *modbus);
+
+       /* TODO See why di->context isn't available yet at this time. */
+       serialcomm = NULL;
+       probe_func = NULL;
+       if (di->context == &rdtech_dps_driver_info || model_type == MODEL_DPS) {
+               serialcomm = default_serialcomm_dps;
+               probe_func = probe_device_dps;
+       }
+       if (di->context == &rdtech_rd_driver_info || model_type == MODEL_RD) {
+               serialcomm = default_serialcomm_rd;
+               probe_func = probe_device_rd;
+       }
+       if (!probe_func)
+               return NULL;
+       if (serialcomm && *serialcomm)
+               default_serialcomm.data = g_variant_new_string(serialcomm);
 
        opts = options;
        if (!g_slist_find_custom(options, &default_serialcomm, config_compare))
@@ -136,7 +203,7 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        if (!g_slist_find_custom(options, &default_modbusaddr, config_compare))
                opts = g_slist_prepend(opts, &default_modbusaddr);
 
-       devices = sr_modbus_scan(di->context, opts, probe_device);
+       devices = sr_modbus_scan(di->context, opts, probe_func);
 
        while (opts != options)
                opts = g_slist_delete_link(opts, opts);
@@ -146,6 +213,16 @@ static GSList *scan(struct sr_dev_driver *di, GSList *options)
        return devices;
 }
 
+static GSList *scan_dps(struct sr_dev_driver *di, GSList *options)
+{
+       return scan(di, options, MODEL_DPS);
+}
+
+static GSList *scan_rd(struct sr_dev_driver *di, GSList *options)
+{
+       return scan(di, options, MODEL_RD);
+}
+
 static int dev_open(struct sr_dev_inst *sdi)
 {
        struct sr_modbus_dev_inst *modbus;
@@ -417,7 +494,7 @@ static struct sr_dev_driver rdtech_dps_driver_info = {
        .api_version = 1,
        .init = std_init,
        .cleanup = std_cleanup,
-       .scan = scan,
+       .scan = scan_dps,
        .dev_list = std_dev_list,
        .dev_clear = std_dev_clear,
        .config_get = config_get,
@@ -430,3 +507,23 @@ static struct sr_dev_driver rdtech_dps_driver_info = {
        .context = NULL,
 };
 SR_REGISTER_DEV_DRIVER(rdtech_dps_driver_info);
+
+static struct sr_dev_driver rdtech_rd_driver_info = {
+       .name = "rdtech-rd",
+       .longname = "RDTech RD series power supply",
+       .api_version = 1,
+       .init = std_init,
+       .cleanup = std_cleanup,
+       .scan = scan_rd,
+       .dev_list = std_dev_list,
+       .dev_clear = std_dev_clear,
+       .config_get = config_get,
+       .config_set = config_set,
+       .config_list = config_list,
+       .dev_open = dev_open,
+       .dev_close = dev_close,
+       .dev_acquisition_start = dev_acquisition_start,
+       .dev_acquisition_stop = dev_acquisition_stop,
+       .context = NULL,
+};
+SR_REGISTER_DEV_DRIVER(rdtech_rd_driver_info);
index d8bd5e82175fe979312df61909966d534a012938..9f45fc4f6d8fa974f37b78b807859faabd5f3e9b 100644 (file)
 #include "protocol.h"
 
 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. */
+       REG_DPS_USET       = 0x00, /* Mirror of 0x50 */
+       REG_DPS_ISET       = 0x01, /* Mirror of 0x51 */
+       REG_DPS_UOUT       = 0x02,
+       REG_DPS_IOUT       = 0x03,
+       REG_DPS_POWER      = 0x04,
+       REG_DPS_UIN        = 0x05,
+       REG_DPS_LOCK       = 0x06,
+       REG_DPS_PROTECT    = 0x07,
+       REG_DPS_CV_CC      = 0x08,
+       REG_DPS_ENABLE     = 0x09,
+       REG_DPS_BACKLIGHT  = 0x0A, /* Mirror of 0x55 */
+       REG_DPS_MODEL      = 0x0B,
+       REG_DPS_VERSION    = 0x0C,
+
+       REG_DPS_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. */
+       PRE_DPS_USET       = 0x50,
+       PRE_DPS_ISET       = 0x51,
+       PRE_DPS_OVPSET     = 0x52,
+       PRE_DPS_OCPSET     = 0x53,
+       PRE_DPS_OPPSET     = 0x54,
+       PRE_DPS_BACKLIGHT  = 0x55,
+       PRE_DPS_DISABLE    = 0x56, /* Disable output if 0 is copied here from a preset (1 is no change). */
+       PRE_DPS_BOOT       = 0x57, /* Enable output at boot if 1. */
 };
-#define REG_PRESET_STRIDE 0x10
+#define PRE_DPS_STRIDE 0x10
 
 enum rdtech_dps_protect_state {
        STATE_NORMAL = 0,
@@ -69,6 +69,37 @@ enum rdtech_dps_regulation_mode {
        MODE_CC      = 1,
 };
 
+enum rdtech_rd_register {
+       REG_RD_MODEL = 0, /* u16 */
+       REG_RD_SERIAL = 1, /* u32 */
+       REG_RD_FIRMWARE = 3, /* u16 */
+       REG_RD_TEMP_INT = 4, /* 2x u16 */
+       REG_RD_TEMP_INT_F = 6, /* 2x u16 */
+       REG_RD_VOLT_TGT = 8, /* u16 */
+       REG_RD_CURR_LIM = 9, /* u16 */
+       REG_RD_VOLTAGE = 10, /* u16 */
+       REG_RD_CURRENT = 11, /* u16 */
+       REG_RD_ENERGY = 12, /* u16 */
+       REG_RD_POWER = 13, /* u16 */
+       REG_RD_VOLT_IN = 14, /* u16 */
+       REG_RD_PROTECT = 16, /* u16 */
+       REG_RD_REGULATION = 17, /* u16 */
+       REG_RD_ENABLE = 18, /* u16 */
+       /*
+        * Battery at 32 == 0x20 pp:
+        * Mode, voltage, temperature, capacity, energy.
+        */
+       /*
+        * Date/time at 48 == 0x30 pp:
+        * Year, month, day, hour, minute, second.
+        */
+       /* Backlight at 72 == 0x48. */
+       REG_RD_OVP_THR = 82, /* 0x52 */
+       REG_RD_OCP_THR = 83, /* 0x53 */
+       /* One "live" slot and 9 "memory" positions. */
+       REG_RD_START_MEM = 84, /* 0x54 */
+};
+
 /* Retries failed modbus read attempts for improved reliability. */
 static int rdtech_dps_read_holding_registers(struct sr_modbus_dev_inst *modbus,
        int address, int nb_registers, uint16_t *registers)
@@ -87,32 +118,32 @@ static int rdtech_dps_read_holding_registers(struct sr_modbus_dev_inst *modbus,
        return ret;
 }
 
-/* Get one 16bit register. */
-static int rdtech_dps_get_reg(const struct sr_dev_inst *sdi,
-       uint16_t address, uint16_t *value)
+/* Set one 16bit register. LE format for DPS devices. */
+static int rdtech_dps_set_reg(const struct sr_dev_inst *sdi,
+       uint16_t address, uint16_t value)
 {
        struct dev_context *devc;
        struct sr_modbus_dev_inst *modbus;
        uint16_t registers[1];
        int ret;
-       const uint8_t *rdptr;
+       uint8_t *wrptr;
 
        devc = sdi->priv;
        modbus = sdi->conn;
 
+       wrptr = (void *)registers;
+       write_u16le(wrptr, value);
+
        g_mutex_lock(&devc->rw_mutex);
-       ret = rdtech_dps_read_holding_registers(modbus,
-               address, ARRAY_SIZE(registers), registers);
+       ret = sr_modbus_write_multiple_registers(modbus, address,
+               ARRAY_SIZE(registers), registers);
        g_mutex_unlock(&devc->rw_mutex);
 
-       rdptr = (void *)registers;
-       *value = read_u16le(rdptr);
-
        return ret;
 }
 
-/* Set one 16bit register. */
-static int rdtech_dps_set_reg(const struct sr_dev_inst *sdi,
+/* Set one 16bit register. BE format for RD devices. */
+static int rdtech_rd_set_reg(const struct sr_dev_inst *sdi,
        uint16_t address, uint16_t value)
 {
        struct dev_context *devc;
@@ -125,7 +156,7 @@ static int rdtech_dps_set_reg(const struct sr_dev_inst *sdi,
        modbus = sdi->conn;
 
        wrptr = (void *)registers;
-       write_u16le(wrptr, value);
+       write_u16be(wrptr, value);
 
        g_mutex_lock(&devc->rw_mutex);
        ret = sr_modbus_write_multiple_registers(modbus, address,
@@ -137,30 +168,49 @@ static int rdtech_dps_set_reg(const struct sr_dev_inst *sdi,
 
 /* Get DPS model number and firmware version from a connected device. */
 SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus,
-       uint16_t *model, uint16_t *version)
+       enum rdtech_dps_model_type model_type,
+       uint16_t *model, uint16_t *version, uint32_t *serno)
 {
-       uint16_t registers[REG_VERSION + 1 - REG_MODEL];
+       uint16_t registers[4];
        int ret;
        const uint8_t *rdptr;
 
-       /* Silence a compiler warning about an unused routine. */
-       (void)rdtech_dps_get_reg;
-
        /*
-        * Get the MODEL and VERSION registers. No mutex here, because
-        * there is no sr_dev_inst when this function is called.
+        * No mutex here because when the routine executes then the
+        * device instance was not created yet (probe phase).
         */
-       ret = rdtech_dps_read_holding_registers(modbus,
-               REG_MODEL, ARRAY_SIZE(registers), registers);
-       if (ret != SR_OK)
-               return ret;
-
-       rdptr = (void *)registers;
-       *model = read_u16le_inc(&rdptr);
-       *version = read_u16le_inc(&rdptr);
-       sr_info("RDTech DPS PSU model: %u version: %u", *model, *version);
-
-       return ret;
+       switch (model_type) {
+       case MODEL_DPS:
+               /* Get the MODEL and VERSION registers. */
+               ret = rdtech_dps_read_holding_registers(modbus,
+                       REG_DPS_MODEL, 2, registers);
+               if (ret != SR_OK)
+                       return ret;
+               rdptr = (void *)registers;
+               *model = read_u16le_inc(&rdptr);
+               *version = read_u16le_inc(&rdptr);
+               *serno = 0;
+               sr_info("RDTech DPS/DPH model: %u version: %u",
+                       *model, *version);
+               return SR_OK;
+       case MODEL_RD:
+               /* Get the MODEL, SERIAL, and FIRMWARE registers. */
+               ret = rdtech_dps_read_holding_registers(modbus,
+                       REG_RD_MODEL, 4, registers);
+               if (ret != SR_OK)
+                       return ret;
+               rdptr = (void *)registers;
+               *model = read_u16be_inc(&rdptr) / 10;
+               *serno = read_u32be_inc(&rdptr);
+               *version = read_u16be_inc(&rdptr);
+               sr_info("RDTech RD model: %u version: %u, serno %u",
+                       *model, *version, *serno);
+               return SR_OK;
+       default:
+               sr_err("Unexpected RDTech PSU device type. Programming error?");
+               return SR_ERR_ARG;
+       }
+       /* UNREACH */
 }
 
 /* Send a measured value to the session feed. */
@@ -208,7 +258,7 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi,
 {
        struct dev_context *devc;
        struct sr_modbus_dev_inst *modbus;
-       uint16_t registers[REG_ENABLE + 1 - REG_USET];
+       uint16_t registers[12];
        int ret;
        const uint8_t *rdptr;
        uint16_t uset_raw, iset_raw, uout_raw, iout_raw, power_raw;
@@ -226,57 +276,122 @@ SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi,
        if (!state)
                return SR_ERR_ARG;
 
-       /* Transfer a chunk of registers in a single call. */
-       g_mutex_lock(&devc->rw_mutex);
-       ret = rdtech_dps_read_holding_registers(modbus,
-               REG_USET, ARRAY_SIZE(registers), registers);
-       g_mutex_unlock(&devc->rw_mutex);
-       if (ret != SR_OK)
-               return ret;
+       switch (devc->model->model_type) {
+       case MODEL_DPS:
+               /*
+                * Transfer a chunk of registers in a single call. It's
+                * unfortunate that the model dependency and the sparse
+                * register map force us to open code addresses, sizes,
+                * and the sequence of the registers and how to interpret
+                * their bit fields. But then this is not too unusual for
+                * a hardware specific device driver ...
+                *
+                * TODO Optionally reduce the transfer volume depending
+                * on the caller specified state query context.
+                */
+               g_mutex_lock(&devc->rw_mutex);
+               ret = rdtech_dps_read_holding_registers(modbus,
+                       REG_DPS_USET, 10, registers);
+               g_mutex_unlock(&devc->rw_mutex);
+               if (ret != SR_OK)
+                       return ret;
 
-       /* Interpret the registers' values. */
-       rdptr = (const void *)registers;
-       uset_raw = read_u16le_inc(&rdptr);
-       volt_target = uset_raw / devc->voltage_multiplier;
-       iset_raw = read_u16le_inc(&rdptr);
-       curr_limit = iset_raw / devc->current_multiplier;
-       uout_raw = read_u16le_inc(&rdptr);
-       curr_voltage = uout_raw / devc->voltage_multiplier;
-       iout_raw = read_u16le_inc(&rdptr);
-       curr_current = iout_raw / devc->current_multiplier;
-       power_raw = read_u16le_inc(&rdptr);
-       curr_power = power_raw / 100.0f;
-       (void)read_u16le_inc(&rdptr); /* UIN */
-       reg_val = read_u16le_inc(&rdptr); /* LOCK */
-       is_lock = reg_val != 0;
-       reg_val = read_u16le_inc(&rdptr); /* PROTECT */
-       uses_ovp = reg_val == STATE_OVP;
-       uses_ocp = reg_val == STATE_OCP;
-       reg_state = read_u16le_inc(&rdptr); /* CV_CC */
-       is_reg_cc = reg_state == MODE_CC;
-       out_state = read_u16le_inc(&rdptr); /* ENABLE */
-       is_out_enabled = out_state != 0;
+               /* Interpret the registers' values. */
+               rdptr = (const void *)registers;
+               uset_raw = read_u16le_inc(&rdptr);
+               volt_target = uset_raw / devc->voltage_multiplier;
+               iset_raw = read_u16le_inc(&rdptr);
+               curr_limit = iset_raw / devc->current_multiplier;
+               uout_raw = read_u16le_inc(&rdptr);
+               curr_voltage = uout_raw / devc->voltage_multiplier;
+               iout_raw = read_u16le_inc(&rdptr);
+               curr_current = iout_raw / devc->current_multiplier;
+               power_raw = read_u16le_inc(&rdptr);
+               curr_power = power_raw / 100.0f;
+               (void)read_u16le_inc(&rdptr); /* UIN */
+               reg_val = read_u16le_inc(&rdptr); /* LOCK */
+               is_lock = reg_val != 0;
+               reg_val = read_u16le_inc(&rdptr); /* PROTECT */
+               uses_ovp = reg_val == STATE_OVP;
+               uses_ocp = reg_val == STATE_OCP;
+               reg_state = read_u16le_inc(&rdptr); /* CV_CC */
+               is_reg_cc = reg_state == MODE_CC;
+               out_state = read_u16le_inc(&rdptr); /* ENABLE */
+               is_out_enabled = out_state != 0;
+
+               /* Transfer another chunk of registers in a single call. */
+               g_mutex_lock(&devc->rw_mutex);
+               ret = rdtech_dps_read_holding_registers(modbus,
+                       PRE_DPS_OVPSET, 2, registers);
+               g_mutex_unlock(&devc->rw_mutex);
+               if (ret != SR_OK)
+                       return ret;
 
-       /*
-        * Transfer another chunk of registers in a single call.
-        * TODO Unfortunately this call site open codes a fixed number
-        * of registers to read. But there is already some leakage of
-        * the register layout in this routine, and adding more device
-        * types in the future will make things "worse". So accept it.
-        */
-       g_mutex_lock(&devc->rw_mutex);
-       ret = rdtech_dps_read_holding_registers(modbus,
-               PRE_OVPSET, 2, registers);
-       g_mutex_unlock(&devc->rw_mutex);
-       if (ret != SR_OK)
-               return ret;
+               /* Interpret the second registers chunk's values. */
+               rdptr = (const void *)registers;
+               ovpset_raw = read_u16le_inc(&rdptr); /* PRE OVPSET */
+               ovp_threshold = ovpset_raw * devc->voltage_multiplier;
+               ocpset_raw = read_u16le_inc(&rdptr); /* PRE OCPSET */
+               ocp_threshold = ocpset_raw * devc->current_multiplier;
+
+               break;
+
+       case MODEL_RD:
+               /* 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);
+               g_mutex_unlock(&devc->rw_mutex);
+               if (ret != SR_OK)
+                       return ret;
+
+               /* Interpret the registers' raw content. */
+               rdptr = (const void *)registers;
+               uset_raw = read_u16be_inc(&rdptr); /* USET */
+               volt_target = uset_raw / devc->voltage_multiplier;
+               iset_raw = read_u16be_inc(&rdptr); /* ISET */
+               curr_limit = iset_raw / devc->current_multiplier;
+               uout_raw = read_u16be_inc(&rdptr); /* UOUT */
+               curr_voltage = uout_raw / devc->voltage_multiplier;
+               iout_raw = read_u16be_inc(&rdptr); /* IOUT */
+               curr_current = iout_raw / devc->current_multiplier;
+               (void)read_u16be_inc(&rdptr); /* ENERGY */
+               power_raw = read_u16be_inc(&rdptr); /* POWER */
+               curr_power = power_raw / 100.0f;
+               (void)read_u16be_inc(&rdptr); /* VOLT_IN */
+               (void)read_u16be_inc(&rdptr);
+               reg_val = read_u16be_inc(&rdptr); /* PROTECT */
+               uses_ovp = reg_val == STATE_OVP;
+               uses_ocp = reg_val == STATE_OCP;
+               reg_state = read_u16be_inc(&rdptr); /* REGULATION */
+               is_reg_cc = reg_state == MODE_CC;
+               out_state = read_u16be_inc(&rdptr); /* ENABLE */
+               is_out_enabled = out_state != 0;
+
+               /* Retrieve a set of adjacent registers. */
+               g_mutex_lock(&devc->rw_mutex);
+               ret = rdtech_dps_read_holding_registers(modbus,
+                       REG_RD_OVP_THR, 2, registers);
+               g_mutex_unlock(&devc->rw_mutex);
+               if (ret != SR_OK)
+                       return ret;
+
+               /* Interpret the registers' raw content. */
+               rdptr = (const void *)registers;
+               ovpset_raw = read_u16be_inc(&rdptr); /* OVP THR */
+               ovp_threshold = ovpset_raw / devc->voltage_multiplier;
+               ocpset_raw = read_u16be_inc(&rdptr); /* OCP THR */
+               ocp_threshold = ocpset_raw / devc->current_multiplier;
 
-       /* Interpret the registers' values. */
-       rdptr = (const void *)registers;
-       ovpset_raw = read_u16le_inc(&rdptr); /* PRE OVPSET */
-       ovp_threshold = ovpset_raw * devc->voltage_multiplier;
-       ocpset_raw = read_u16le_inc(&rdptr); /* PRE OCPSET */
-       ocp_threshold = ocpset_raw * devc->current_multiplier;
+               /* Details which we cannot query from the device. */
+               is_lock = FALSE;
+
+               break;
+
+       default:
+               /* ShouldNotHappen(TM). Probe should have failed. */
+               return SR_ERR_ARG;
+       }
 
        /* Store gathered details in the high level container. */
        memset(state, 0, sizeof(*state));
@@ -327,39 +442,103 @@ SR_PRIV int rdtech_dps_set_state(const struct sr_dev_inst *sdi,
        /* Only a subset of known values is settable. */
        if (state->mask & STATE_OUTPUT_ENABLED) {
                reg_value = state->output_enabled ? 1 : 0;
-               ret = rdtech_dps_set_reg(sdi, REG_ENABLE, reg_value);
-               if (ret != SR_OK)
-                       return ret;
+               switch (devc->model->model_type) {
+               case MODEL_DPS:
+                       ret = rdtech_dps_set_reg(sdi, REG_DPS_ENABLE, reg_value);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               case MODEL_RD:
+                       ret = rdtech_rd_set_reg(sdi, REG_RD_ENABLE, reg_value);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               default:
+                       return SR_ERR_ARG;
+               }
        }
        if (state->mask & STATE_VOLTAGE_TARGET) {
                reg_value = state->voltage_target * devc->voltage_multiplier;
-               ret = rdtech_dps_set_reg(sdi, REG_USET, reg_value);
-               if (ret != SR_OK)
-                       return ret;
+               switch (devc->model->model_type) {
+               case MODEL_DPS:
+                       ret = rdtech_dps_set_reg(sdi, REG_DPS_USET, reg_value);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               case MODEL_RD:
+                       ret = rdtech_rd_set_reg(sdi, REG_RD_VOLT_TGT, reg_value);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               default:
+                       return SR_ERR_ARG;
+               }
        }
        if (state->mask & STATE_CURRENT_LIMIT) {
                reg_value = state->current_limit * devc->current_multiplier;
-               ret = rdtech_dps_set_reg(sdi, REG_ISET, reg_value);
-               if (ret != SR_OK)
-                       return ret;
+               switch (devc->model->model_type) {
+               case MODEL_DPS:
+                       ret = rdtech_dps_set_reg(sdi, REG_DPS_ISET, reg_value);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               case MODEL_RD:
+                       ret = rdtech_rd_set_reg(sdi, REG_RD_CURR_LIM, reg_value);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               default:
+                       return SR_ERR_ARG;
+               }
        }
        if (state->mask & STATE_OVP_THRESHOLD) {
                reg_value = state->ovp_threshold * devc->voltage_multiplier;
-               ret = rdtech_dps_set_reg(sdi, PRE_OVPSET, reg_value);
-               if (ret != SR_OK)
-                       return ret;
+               switch (devc->model->model_type) {
+               case MODEL_DPS:
+                       ret = rdtech_dps_set_reg(sdi, PRE_DPS_OVPSET, reg_value);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               case MODEL_RD:
+                       ret = rdtech_rd_set_reg(sdi, REG_RD_OVP_THR, reg_value);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               default:
+                       return SR_ERR_ARG;
+               }
        }
        if (state->mask & STATE_OCP_THRESHOLD) {
                reg_value = state->ocp_threshold * devc->current_multiplier;
-               ret = rdtech_dps_set_reg(sdi, PRE_OCPSET, reg_value);
-               if (ret != SR_OK)
-                       return ret;
+               switch (devc->model->model_type) {
+               case MODEL_DPS:
+                       ret = rdtech_dps_set_reg(sdi, PRE_DPS_OCPSET, reg_value);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               case MODEL_RD:
+                       ret = rdtech_rd_set_reg(sdi, REG_RD_OCP_THR, reg_value);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               default:
+                       return SR_ERR_ARG;
+               }
        }
        if (state->mask & STATE_LOCK) {
-               reg_value = state->lock ? 1 : 0;
-               ret = rdtech_dps_set_reg(sdi, REG_LOCK, reg_value);
-               if (ret != SR_OK)
-                       return ret;
+               switch (devc->model->model_type) {
+               case MODEL_DPS:
+                       reg_value = state->lock ? 1 : 0;
+                       ret = rdtech_dps_set_reg(sdi, REG_DPS_LOCK, reg_value);
+                       if (ret != SR_OK)
+                               return ret;
+                       break;
+               case MODEL_RD:
+                       /* Do nothing, _and_ silently succeed. */
+                       break;
+               default:
+                       return SR_ERR_ARG;
+               }
        }
 
        return SR_OK;
index fc23ed3ab1c087e2fdbe86484e330c4728386dc4..26c196ff43755d1585206c0e60df30f6bcf54e03 100644 (file)
 
 #define LOG_PREFIX "rdtech-dps"
 
+enum rdtech_dps_model_type {
+       MODEL_NONE,
+       MODEL_DPS,
+       MODEL_RD,
+};
+
 struct rdtech_dps_model {
+       enum rdtech_dps_model_type model_type;
        unsigned int id;
        const char *name;
        unsigned int max_current;
@@ -85,7 +92,8 @@ SR_PRIV int rdtech_dps_set_state(const struct sr_dev_inst *sdi,
        struct rdtech_dps_state *state);
 
 SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus,
-       uint16_t *model, uint16_t *version);
+       enum rdtech_dps_model_type model_type,
+       uint16_t *model, uint16_t *version, uint32_t *serno);
 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);