]> sigrok.org Git - libsigrok.git/blob - src/hardware/rdtech-dps/protocol.c
rdtech-dps: layer separation between API and protocol, style nits
[libsigrok.git] / src / hardware / rdtech-dps / protocol.c
1 /*
2  * This file is part of the libsigrok project.
3  *
4  * Copyright (C) 2018 James Churchill <pelrun@gmail.com>
5  * Copyright (C) 2019 Frank Stettner <frank-stettner@gmx.net>
6  * Copyright (C) 2021 Gerhard Sittig <gerhard.sittig@gmx.net>
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22 #include <config.h>
23
24 #include <string.h>
25
26 #include "protocol.h"
27
28 enum rdtech_dps_register {
29         REG_USET       = 0x00, /* Mirror of 0x50 */
30         REG_ISET       = 0x01, /* Mirror of 0x51 */
31         REG_UOUT       = 0x02,
32         REG_IOUT       = 0x03,
33         REG_POWER      = 0x04,
34         REG_UIN        = 0x05,
35         REG_LOCK       = 0x06,
36         REG_PROTECT    = 0x07,
37         REG_CV_CC      = 0x08,
38         REG_ENABLE     = 0x09,
39         REG_BACKLIGHT  = 0x0A, /* Mirror of 0x55 */
40         REG_MODEL      = 0x0B,
41         REG_VERSION    = 0x0C,
42
43         REG_PRESET     = 0x23, /* Loads a preset into preset 0. */
44
45         /*
46          * Add (preset * 0x10) to each of the following, for preset 1-9.
47          * Preset 0 regs below are the active output settings.
48          */
49         PRE_USET       = 0x50,
50         PRE_ISET       = 0x51,
51         PRE_OVPSET     = 0x52,
52         PRE_OCPSET     = 0x53,
53         PRE_OPPSET     = 0x54,
54         PRE_BACKLIGHT  = 0x55,
55         PRE_DISABLE    = 0x56, /* Disable output if 0 is copied here from a preset (1 is no change). */
56         PRE_BOOT       = 0x57, /* Enable output at boot if 1. */
57 };
58 #define REG_PRESET_STRIDE 0x10
59
60 enum rdtech_dps_protect_state {
61         STATE_NORMAL = 0,
62         STATE_OVP    = 1,
63         STATE_OCP    = 2,
64         STATE_OPP    = 3,
65 };
66
67 enum rdtech_dps_regulation_mode {
68         MODE_CV      = 0,
69         MODE_CC      = 1,
70 };
71
72 /* Retries failed modbus read attempts for improved reliability. */
73 static int rdtech_dps_read_holding_registers(struct sr_modbus_dev_inst *modbus,
74         int address, int nb_registers, uint16_t *registers)
75 {
76         size_t retries;
77         int ret;
78
79         retries = 3;
80         while (retries--) {
81                 ret = sr_modbus_read_holding_registers(modbus,
82                         address, nb_registers, registers);
83                 if (ret == SR_OK)
84                         return ret;
85         }
86
87         return ret;
88 }
89
90 /* Get one 16bit register. */
91 static int rdtech_dps_get_reg(const struct sr_dev_inst *sdi,
92         uint16_t address, uint16_t *value)
93 {
94         struct dev_context *devc;
95         struct sr_modbus_dev_inst *modbus;
96         uint16_t registers[1];
97         int ret;
98         const uint8_t *rdptr;
99
100         devc = sdi->priv;
101         modbus = sdi->conn;
102
103         g_mutex_lock(&devc->rw_mutex);
104         ret = rdtech_dps_read_holding_registers(modbus,
105                 address, ARRAY_SIZE(registers), registers);
106         g_mutex_unlock(&devc->rw_mutex);
107
108         rdptr = (void *)registers;
109         *value = read_u16le(rdptr);
110
111         return ret;
112 }
113
114 /* Set one 16bit register. */
115 static int rdtech_dps_set_reg(const struct sr_dev_inst *sdi,
116         uint16_t address, uint16_t value)
117 {
118         struct dev_context *devc;
119         struct sr_modbus_dev_inst *modbus;
120         uint16_t registers[1];
121         int ret;
122         uint8_t *wrptr;
123
124         devc = sdi->priv;
125         modbus = sdi->conn;
126
127         wrptr = (void *)registers;
128         write_u16le(wrptr, value);
129
130         g_mutex_lock(&devc->rw_mutex);
131         ret = sr_modbus_write_multiple_registers(modbus, address,
132                 ARRAY_SIZE(registers), registers);
133         g_mutex_unlock(&devc->rw_mutex);
134
135         return ret;
136 }
137
138 /* Get DPS model number and firmware version from a connected device. */
139 SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus,
140         uint16_t *model, uint16_t *version)
141 {
142         uint16_t registers[REG_VERSION + 1 - REG_MODEL];
143         int ret;
144         const uint8_t *rdptr;
145
146         /* Silence a compiler warning about an unused routine. */
147         (void)rdtech_dps_get_reg;
148
149         /*
150          * Get the MODEL and VERSION registers. No mutex here, because
151          * there is no sr_dev_inst when this function is called.
152          */
153         ret = rdtech_dps_read_holding_registers(modbus,
154                 REG_MODEL, ARRAY_SIZE(registers), registers);
155         if (ret != SR_OK)
156                 return ret;
157
158         rdptr = (void *)registers;
159         *model = read_u16le_inc(&rdptr);
160         *version = read_u16le_inc(&rdptr);
161         sr_info("RDTech DPS PSU model: %u version: %u", *model, *version);
162
163         return ret;
164 }
165
166 /* Send a measured value to the session feed. */
167 static int send_value(const struct sr_dev_inst *sdi,
168         struct sr_channel *ch, float value,
169         enum sr_mq mq, enum sr_mqflag mqflags,
170         enum sr_unit unit, int digits)
171 {
172         struct sr_datafeed_packet packet;
173         struct sr_datafeed_analog analog;
174         struct sr_analog_encoding encoding;
175         struct sr_analog_meaning meaning;
176         struct sr_analog_spec spec;
177         int ret;
178
179         sr_analog_init(&analog, &encoding, &meaning, &spec, digits);
180         analog.meaning->channels = g_slist_append(NULL, ch);
181         analog.num_samples = 1;
182         analog.data = &value;
183         analog.meaning->mq = mq;
184         analog.meaning->mqflags = mqflags;
185         analog.meaning->unit = unit;
186
187         packet.type = SR_DF_ANALOG;
188         packet.payload = &analog;
189         ret = sr_session_send(sdi, &packet);
190
191         g_slist_free(analog.meaning->channels);
192
193         return ret;
194 }
195
196 /*
197  * Get the device's current state. Exhaustively, relentlessly.
198  * Concentrate all details of communication in the physical transport,
199  * register layout interpretation, and potential model dependency in
200  * this central spot, to simplify maintenance.
201  *
202  * TODO Optionally limit the transfer volume depending on caller's spec
203  * which detail level is desired? Is 10 registers each 16bits an issue
204  * when the UART bitrate is only 9600bps?
205  */
206 SR_PRIV int rdtech_dps_get_state(const struct sr_dev_inst *sdi,
207         struct rdtech_dps_state *state)
208 {
209         struct dev_context *devc;
210         struct sr_modbus_dev_inst *modbus;
211         uint16_t registers[REG_ENABLE + 1 - REG_USET];
212         int ret;
213         const uint8_t *rdptr;
214         uint16_t uset_raw, iset_raw, uout_raw, iout_raw, power_raw;
215         uint16_t reg_val, reg_state, out_state, ovpset_raw, ocpset_raw;
216         gboolean is_lock, is_out_enabled, is_reg_cc;
217         gboolean uses_ovp, uses_ocp;
218         float volt_target, curr_limit;
219         float ovp_threshold, ocp_threshold;
220         float curr_voltage, curr_current, curr_power;
221
222         if (!sdi || !sdi->priv || !sdi->conn)
223                 return SR_ERR_ARG;
224         devc = sdi->priv;
225         modbus = sdi->conn;
226         if (!state)
227                 return SR_ERR_ARG;
228
229         /* Transfer a chunk of registers in a single call. */
230         g_mutex_lock(&devc->rw_mutex);
231         ret = rdtech_dps_read_holding_registers(modbus,
232                 REG_USET, ARRAY_SIZE(registers), registers);
233         g_mutex_unlock(&devc->rw_mutex);
234         if (ret != SR_OK)
235                 return ret;
236
237         /* Interpret the registers' values. */
238         rdptr = (const void *)registers;
239         uset_raw = read_u16le_inc(&rdptr);
240         volt_target = uset_raw / devc->voltage_multiplier;
241         iset_raw = read_u16le_inc(&rdptr);
242         curr_limit = iset_raw / devc->current_multiplier;
243         uout_raw = read_u16le_inc(&rdptr);
244         curr_voltage = uout_raw / devc->voltage_multiplier;
245         iout_raw = read_u16le_inc(&rdptr);
246         curr_current = iout_raw / devc->current_multiplier;
247         power_raw = read_u16le_inc(&rdptr);
248         curr_power = power_raw / 100.0f;
249         (void)read_u16le_inc(&rdptr); /* UIN */
250         reg_val = read_u16le_inc(&rdptr); /* LOCK */
251         is_lock = reg_val != 0;
252         reg_val = read_u16le_inc(&rdptr); /* PROTECT */
253         uses_ovp = reg_val == STATE_OVP;
254         uses_ocp = reg_val == STATE_OCP;
255         reg_state = read_u16le_inc(&rdptr); /* CV_CC */
256         is_reg_cc = reg_state == MODE_CC;
257         out_state = read_u16le_inc(&rdptr); /* ENABLE */
258         is_out_enabled = out_state != 0;
259
260         /*
261          * Transfer another chunk of registers in a single call.
262          * TODO Unfortunately this call site open codes a fixed number
263          * of registers to read. But there is already some leakage of
264          * the register layout in this routine, and adding more device
265          * types in the future will make things "worse". So accept it.
266          */
267         g_mutex_lock(&devc->rw_mutex);
268         ret = rdtech_dps_read_holding_registers(modbus,
269                 PRE_OVPSET, 2, registers);
270         g_mutex_unlock(&devc->rw_mutex);
271         if (ret != SR_OK)
272                 return ret;
273
274         /* Interpret the registers' values. */
275         rdptr = (const void *)registers;
276         ovpset_raw = read_u16le_inc(&rdptr); /* PRE OVPSET */
277         ovp_threshold = ovpset_raw * devc->voltage_multiplier;
278         ocpset_raw = read_u16le_inc(&rdptr); /* PRE OCPSET */
279         ocp_threshold = ocpset_raw * devc->current_multiplier;
280
281         /* Store gathered details in the high level container. */
282         memset(state, 0, sizeof(*state));
283         state->lock = is_lock;
284         state->mask |= STATE_LOCK;
285         state->output_enabled = is_out_enabled;
286         state->mask |= STATE_OUTPUT_ENABLED;
287         state->regulation_cc = is_reg_cc;
288         state->mask |= STATE_REGULATION_CC;
289         state->protect_ovp = uses_ovp;
290         state->mask |= STATE_PROTECT_OVP;
291         state->protect_ocp = uses_ocp;
292         state->mask |= STATE_PROTECT_OCP;
293         state->protect_enabled = TRUE;
294         state->mask |= STATE_PROTECT_ENABLED;
295         state->voltage_target = volt_target;
296         state->mask |= STATE_VOLTAGE_TARGET;
297         state->current_limit = curr_limit;
298         state->mask |= STATE_CURRENT_LIMIT;
299         state->ovp_threshold = ovp_threshold;
300         state->mask |= STATE_OVP_THRESHOLD;
301         state->ocp_threshold = ocp_threshold;
302         state->mask |= STATE_OCP_THRESHOLD;
303         state->voltage = curr_voltage;
304         state->mask |= STATE_VOLTAGE;
305         state->current = curr_current;
306         state->mask |= STATE_CURRENT;
307         state->power = curr_power;
308         state->mask |= STATE_POWER;
309
310         return SR_OK;
311 }
312
313 /* Setup device's parameters. Selectively, from caller specs. */
314 SR_PRIV int rdtech_dps_set_state(const struct sr_dev_inst *sdi,
315         struct rdtech_dps_state *state)
316 {
317         struct dev_context *devc;
318         uint16_t reg_value;
319         int ret;
320
321         if (!sdi || !sdi->priv || !sdi->conn)
322                 return SR_ERR_ARG;
323         devc = sdi->priv;
324         if (!state)
325                 return SR_ERR_ARG;
326
327         /* Only a subset of known values is settable. */
328         if (state->mask & STATE_OUTPUT_ENABLED) {
329                 reg_value = state->output_enabled ? 1 : 0;
330                 ret = rdtech_dps_set_reg(sdi, REG_ENABLE, reg_value);
331                 if (ret != SR_OK)
332                         return ret;
333         }
334         if (state->mask & STATE_VOLTAGE_TARGET) {
335                 reg_value = state->voltage_target * devc->voltage_multiplier;
336                 ret = rdtech_dps_set_reg(sdi, REG_USET, reg_value);
337                 if (ret != SR_OK)
338                         return ret;
339         }
340         if (state->mask & STATE_CURRENT_LIMIT) {
341                 reg_value = state->current_limit * devc->current_multiplier;
342                 ret = rdtech_dps_set_reg(sdi, REG_ISET, reg_value);
343                 if (ret != SR_OK)
344                         return ret;
345         }
346         if (state->mask & STATE_OVP_THRESHOLD) {
347                 reg_value = state->ovp_threshold * devc->voltage_multiplier;
348                 ret = rdtech_dps_set_reg(sdi, PRE_OVPSET, reg_value);
349                 if (ret != SR_OK)
350                         return ret;
351         }
352         if (state->mask & STATE_OCP_THRESHOLD) {
353                 reg_value = state->ocp_threshold * devc->current_multiplier;
354                 ret = rdtech_dps_set_reg(sdi, PRE_OCPSET, reg_value);
355                 if (ret != SR_OK)
356                         return ret;
357         }
358         if (state->mask & STATE_LOCK) {
359                 reg_value = state->lock ? 1 : 0;
360                 ret = rdtech_dps_set_reg(sdi, REG_LOCK, reg_value);
361                 if (ret != SR_OK)
362                         return ret;
363         }
364
365         return SR_OK;
366 }
367
368 /* Get the current state when acquisition starts. */
369 SR_PRIV int rdtech_dps_seed_receive(const struct sr_dev_inst *sdi)
370 {
371         struct dev_context *devc;
372         struct rdtech_dps_state state;
373         int ret;
374
375         ret = rdtech_dps_get_state(sdi, &state);
376         if (ret != SR_OK)
377                 return ret;
378
379         if (state.mask & STATE_PROTECT_OVP)
380                 devc->curr_ovp_state = state.protect_ovp;
381         if (state.mask & STATE_PROTECT_OCP)
382                 devc->curr_ocp_state = state.protect_ocp;
383         if (state.mask & STATE_REGULATION_CC)
384                 devc->curr_cc_state = state.regulation_cc;
385         if (state.mask & STATE_OUTPUT_ENABLED)
386                 devc->curr_out_state = state.output_enabled;
387
388         return SR_OK;
389 }
390
391 /* Get measurements, track state changes during acquisition. */
392 SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data)
393 {
394         struct sr_dev_inst *sdi;
395         struct dev_context *devc;
396         struct rdtech_dps_state state;
397         int ret;
398         struct sr_channel *ch;
399         const char *regulation_text;
400
401         (void)fd;
402         (void)revents;
403
404         sdi = cb_data;
405         if (!sdi)
406                 return TRUE;
407         devc = sdi->priv;
408
409         /* Get the device's current state. */
410         ret = rdtech_dps_get_state(sdi, &state);
411         if (ret != SR_OK)
412                 return ret;
413
414
415         /* Submit measurement data to the session feed. */
416         std_session_send_df_frame_begin(sdi);
417         ch = g_slist_nth_data(sdi->channels, 0);
418         send_value(sdi, ch, state.voltage,
419                 SR_MQ_VOLTAGE, SR_MQFLAG_DC, SR_UNIT_VOLT,
420                 devc->model->voltage_digits);
421         ch = g_slist_nth_data(sdi->channels, 1);
422         send_value(sdi, ch, state.current,
423                 SR_MQ_CURRENT, SR_MQFLAG_DC, SR_UNIT_AMPERE,
424                 devc->model->current_digits);
425         ch = g_slist_nth_data(sdi->channels, 2);
426         send_value(sdi, ch, state.power,
427                 SR_MQ_POWER, 0, SR_UNIT_WATT, 2);
428         std_session_send_df_frame_end(sdi);
429
430         /* Check for state changes. */
431         if (devc->curr_ovp_state != state.protect_ovp) {
432                 (void)sr_session_send_meta(sdi,
433                         SR_CONF_OVER_VOLTAGE_PROTECTION_ACTIVE,
434                         g_variant_new_boolean(state.protect_ovp));
435                 devc->curr_ovp_state = state.protect_ovp;
436         }
437         if (devc->curr_ocp_state != state.protect_ocp) {
438                 (void)sr_session_send_meta(sdi,
439                         SR_CONF_OVER_CURRENT_PROTECTION_ACTIVE,
440                         g_variant_new_boolean(state.protect_ocp));
441                 devc->curr_ocp_state = state.protect_ocp;
442         }
443         if (devc->curr_cc_state != state.regulation_cc) {
444                 regulation_text = state.regulation_cc ? "CC" : "CV";
445                 (void)sr_session_send_meta(sdi, SR_CONF_REGULATION,
446                         g_variant_new_string(regulation_text));
447                 devc->curr_cc_state = state.regulation_cc;
448         }
449         if (devc->curr_out_state != state.output_enabled) {
450                 (void)sr_session_send_meta(sdi, SR_CONF_ENABLED,
451                         g_variant_new_boolean(state.output_enabled));
452                 devc->curr_out_state = state.output_enabled;
453         }
454
455         /* Check optional acquisition limits. */
456         sr_sw_limits_update_samples_read(&devc->limits, 1);
457         if (sr_sw_limits_check(&devc->limits)) {
458                 sr_dev_acquisition_stop(sdi);
459                 return TRUE;
460         }
461
462         return TRUE;
463 }