]> sigrok.org Git - libsigrok.git/blame - src/hardware/rdtech-dps/protocol.c
rdtech-dps: layer separation between API and protocol, style nits
[libsigrok.git] / src / hardware / rdtech-dps / protocol.c
CommitLineData
0549416e
JC
1/*
2 * This file is part of the libsigrok project.
3 *
4 * Copyright (C) 2018 James Churchill <pelrun@gmail.com>
7c0891b0 5 * Copyright (C) 2019 Frank Stettner <frank-stettner@gmx.net>
d7a4dad8 6 * Copyright (C) 2021 Gerhard Sittig <gerhard.sittig@gmx.net>
0549416e
JC
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>
d7a4dad8
GS
23
24#include <string.h>
25
0549416e
JC
26#include "protocol.h"
27
d7a4dad8
GS
28enum 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
60enum rdtech_dps_protect_state {
61 STATE_NORMAL = 0,
62 STATE_OVP = 1,
63 STATE_OCP = 2,
64 STATE_OPP = 3,
65};
66
67enum rdtech_dps_regulation_mode {
68 MODE_CV = 0,
69 MODE_CC = 1,
70};
71
72/* Retries failed modbus read attempts for improved reliability. */
73static int rdtech_dps_read_holding_registers(struct sr_modbus_dev_inst *modbus,
74 int address, int nb_registers, uint16_t *registers)
aff20941 75{
d7a4dad8
GS
76 size_t retries;
77 int ret;
aff20941 78
d7a4dad8
GS
79 retries = 3;
80 while (retries--) {
aff20941
FS
81 ret = sr_modbus_read_holding_registers(modbus,
82 address, nb_registers, registers);
d7a4dad8
GS
83 if (ret == SR_OK)
84 return ret;
85 }
aff20941
FS
86
87 return ret;
88}
89
d7a4dad8
GS
90/* Get one 16bit register. */
91static int rdtech_dps_get_reg(const struct sr_dev_inst *sdi,
92 uint16_t address, uint16_t *value)
69b05583 93{
7c0891b0
FS
94 struct dev_context *devc;
95 struct sr_modbus_dev_inst *modbus;
69b05583 96 uint16_t registers[1];
7c0891b0 97 int ret;
d7a4dad8 98 const uint8_t *rdptr;
7c0891b0
FS
99
100 devc = sdi->priv;
101 modbus = sdi->conn;
102
103 g_mutex_lock(&devc->rw_mutex);
d7a4dad8
GS
104 ret = rdtech_dps_read_holding_registers(modbus,
105 address, ARRAY_SIZE(registers), registers);
7c0891b0 106 g_mutex_unlock(&devc->rw_mutex);
d7a4dad8
GS
107
108 rdptr = (void *)registers;
109 *value = read_u16le(rdptr);
110
69b05583
JC
111 return ret;
112}
113
d7a4dad8
GS
114/* Set one 16bit register. */
115static int rdtech_dps_set_reg(const struct sr_dev_inst *sdi,
116 uint16_t address, uint16_t value)
69b05583 117{
7c0891b0
FS
118 struct dev_context *devc;
119 struct sr_modbus_dev_inst *modbus;
69b05583 120 uint16_t registers[1];
7c0891b0 121 int ret;
d7a4dad8 122 uint8_t *wrptr;
7c0891b0
FS
123
124 devc = sdi->priv;
125 modbus = sdi->conn;
126
d7a4dad8
GS
127 wrptr = (void *)registers;
128 write_u16le(wrptr, value);
129
7c0891b0 130 g_mutex_lock(&devc->rw_mutex);
d7a4dad8
GS
131 ret = sr_modbus_write_multiple_registers(modbus, address,
132 ARRAY_SIZE(registers), registers);
7c0891b0 133 g_mutex_unlock(&devc->rw_mutex);
d7a4dad8 134
7c0891b0 135 return ret;
69b05583
JC
136}
137
d7a4dad8 138/* Get DPS model number and firmware version from a connected device. */
69b05583 139SR_PRIV int rdtech_dps_get_model_version(struct sr_modbus_dev_inst *modbus,
d7a4dad8 140 uint16_t *model, uint16_t *version)
69b05583 141{
d7a4dad8 142 uint16_t registers[REG_VERSION + 1 - REG_MODEL];
69b05583 143 int ret;
d7a4dad8
GS
144 const uint8_t *rdptr;
145
146 /* Silence a compiler warning about an unused routine. */
147 (void)rdtech_dps_get_reg;
7c0891b0
FS
148
149 /*
d7a4dad8
GS
150 * Get the MODEL and VERSION registers. No mutex here, because
151 * there is no sr_dev_inst when this function is called.
7c0891b0 152 */
d7a4dad8
GS
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
69b05583
JC
163 return ret;
164}
165
d7a4dad8
GS
166/* Send a measured value to the session feed. */
167static 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)
69b05583
JC
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;
d7a4dad8 177 int ret;
69b05583
JC
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;
c9b187a6 184 analog.meaning->mqflags = mqflags;
69b05583 185 analog.meaning->unit = unit;
69b05583
JC
186
187 packet.type = SR_DF_ANALOG;
188 packet.payload = &analog;
d7a4dad8
GS
189 ret = sr_session_send(sdi, &packet);
190
69b05583 191 g_slist_free(analog.meaning->channels);
d7a4dad8
GS
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 */
206SR_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. */
314SR_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;
69b05583
JC
366}
367
d7a4dad8
GS
368/* Get the current state when acquisition starts. */
369SR_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. */
0549416e
JC
392SR_PRIV int rdtech_dps_receive_data(int fd, int revents, void *cb_data)
393{
69b05583 394 struct sr_dev_inst *sdi;
0549416e 395 struct dev_context *devc;
d7a4dad8 396 struct rdtech_dps_state state;
7c0891b0 397 int ret;
d7a4dad8
GS
398 struct sr_channel *ch;
399 const char *regulation_text;
0549416e
JC
400
401 (void)fd;
69b05583 402 (void)revents;
0549416e 403
d7a4dad8
GS
404 sdi = cb_data;
405 if (!sdi)
0549416e 406 return TRUE;
69b05583
JC
407 devc = sdi->priv;
408
d7a4dad8
GS
409 /* Get the device's current state. */
410 ret = rdtech_dps_get_state(sdi, &state);
411 if (ret != SR_OK)
412 return ret;
7c0891b0 413
d7a4dad8
GS
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;
69b05583
JC
453 }
454
d7a4dad8
GS
455 /* Check optional acquisition limits. */
456 sr_sw_limits_update_samples_read(&devc->limits, 1);
69b05583
JC
457 if (sr_sw_limits_check(&devc->limits)) {
458 sr_dev_acquisition_stop(sdi);
459 return TRUE;
0549416e
JC
460 }
461
462 return TRUE;
463}