Skip to content

Commit 86a5112

Browse files
evgeny-bogersikmir
authored andcommitted
scpi-dmm: support for ranges for OWON XDM DMMs
All methods are implemented: get, set and list. Human-readable names, the same as displayed on the screen, are used. Tested with sigrok-cli and smuview
1 parent 61b7068 commit 86a5112

File tree

3 files changed

+306
-7
lines changed

3 files changed

+306
-7
lines changed

src/hardware/scpi-dmm/api.c

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ static const struct scpi_command cmdset_owon[] = {
123123
{ DMM_CMD_SETUP_FUNC, "CONF:%s", },
124124
{ DMM_CMD_QUERY_FUNC, "FUNC?", },
125125
{ DMM_CMD_QUERY_VALUE, "MEAS1?", },
126+
{ DMM_CMD_SETUP_RANGE, "CONF:%s %s", },
127+
{ DMM_CMD_QUERY_RANGE, "RANGE?", },
128+
{ DMM_CMD_QUERY_RANGE_AUTO, "AUTO?", },
126129
ALL_ZERO,
127130
};
128131

@@ -206,10 +209,10 @@ static const struct mqopt_item mqopts_owon_xdm2041[] = {
206209
{ SR_MQ_CURRENT, SR_MQFLAG_DC, "CURR:DC", "CURR", NO_DFLT_PREC, FLAGS_NONE, },
207210
{ SR_MQ_RESISTANCE, 0, "RES", "RES", NO_DFLT_PREC, FLAGS_NONE, },
208211
{ SR_MQ_RESISTANCE, SR_MQFLAG_FOUR_WIRE, "FRES", "FRES", NO_DFLT_PREC, FLAGS_NONE, },
209-
{ SR_MQ_CONTINUITY, 0, "CONT", "CONT", -1, FLAGS_NONE, },
210-
{ SR_MQ_VOLTAGE, SR_MQFLAG_DC | SR_MQFLAG_DIODE, "DIOD", "DIOD", -4, FLAGS_NONE, },
212+
{ SR_MQ_CONTINUITY, 0, "CONT", "CONT", -1, FLAG_NO_RANGE, },
213+
{ SR_MQ_VOLTAGE, SR_MQFLAG_DC | SR_MQFLAG_DIODE, "DIOD", "DIOD", -4, FLAG_NO_RANGE, },
211214
{ SR_MQ_TEMPERATURE, 0, "TEMP", "TEMP", NO_DFLT_PREC, FLAGS_NONE, },
212-
{ SR_MQ_FREQUENCY, 0, "FREQ", "FREQ", NO_DFLT_PREC, FLAGS_NONE, },
215+
{ SR_MQ_FREQUENCY, 0, "FREQ", "FREQ", NO_DFLT_PREC, FLAG_NO_RANGE, },
213216
{ SR_MQ_CAPACITANCE, 0, "CAP", "CAP", NO_DFLT_PREC, FLAGS_NONE, },
214217
};
215218

@@ -305,17 +308,17 @@ SR_PRIV const struct scpi_dmm_model models[] = {
305308
"OWON", "XDM1041",
306309
1, 5, cmdset_owon, ARRAY_AND_SIZE(mqopts_owon_xdm1041),
307310
scpi_dmm_get_meas_gwinstek,
308-
ARRAY_AND_SIZE(devopts_generic),
311+
ARRAY_AND_SIZE(devopts_generic_range),
309312
0, 0, 0, 1e9, TRUE,
310-
NULL, NULL, NULL,
313+
scpi_dmm_owon_get_range_text, scpi_dmm_owon_set_range_from_text, scpi_dmm_owon_get_range_text_list,
311314
},
312315
{
313316
"OWON", "XDM2041",
314317
1, 5, cmdset_owon, ARRAY_AND_SIZE(mqopts_owon_xdm2041),
315318
scpi_dmm_get_meas_gwinstek,
316-
ARRAY_AND_SIZE(devopts_generic),
319+
ARRAY_AND_SIZE(devopts_generic_range),
317320
0, 0, 0, 1e9, TRUE,
318-
NULL, NULL, NULL,
321+
scpi_dmm_owon_get_range_text, scpi_dmm_owon_set_range_from_text, scpi_dmm_owon_get_range_text_list,
319322
},
320323
{
321324
"Siglent", "SDM3055",

src/hardware/scpi-dmm/protocol.c

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,35 @@
2424

2525
#define WITH_CMD_DELAY 0 /* TODO See which devices need delays. */
2626

27+
/* OWON XDM range definitions */
28+
static const char *owon_dcv_ranges[] = {
29+
"auto", "50 mV", "500 mV", "5 V", "50 V", "500 V", "1000 V", NULL
30+
};
31+
32+
static const char *owon_acv_ranges[] = {
33+
"auto", "500 mV", "5 V", "50 V", "500 V", "750 V", NULL
34+
};
35+
36+
static const char *owon_dci_ranges[] = {
37+
"auto", "500 uA", "5 mA", "50 mA", "500 mA", "5 A", "10 A", NULL
38+
};
39+
40+
static const char *owon_aci_ranges[] = {
41+
"auto", "500 uA", "5 mA", "50 mA", "500 mA", "5 A", "10 A", NULL
42+
};
43+
44+
static const char *owon_res_ranges[] = {
45+
"auto", "500 Ohm", "5 kOhm", "50 kOhm", "500 kOhm", "5 MOhm", "50 MOhm", NULL
46+
};
47+
48+
static const char *owon_cap_ranges[] = {
49+
"auto", "50 nF", "500 nF", "5 uF", "50 uF", "500 uF", "5 mF", "50 mF", NULL
50+
};
51+
52+
static const char *owon_temp_ranges[] = {
53+
"KITS90", "Pt100", NULL /* no auto here */
54+
};
55+
2756
SR_PRIV void scpi_dmm_cmd_delay(struct sr_scpi_dev_inst *scpi)
2857
{
2958
if (WITH_CMD_DELAY)
@@ -216,6 +245,99 @@ SR_PRIV const char *scpi_dmm_get_range_text(const struct sr_dev_inst *sdi)
216245
return devc->range_text;
217246
}
218247

248+
/*
249+
* We use human-readable range texts, including the unit. They are mostly the same
250+
* as displayed on the device, but with some differences:
251+
* - The unit is always separated from the number by a space.
252+
* - The Unicode Omega symbol (0xCE 0xA9) is replaced with "Ohm".
253+
*
254+
* Here is all the possible range answers, that I got from XDM1041 over SCPI:
255+
* DCV: 1000 V␍␊ 500 V␍␊ 50 V␍␊ 5 V␍␊ 500 mV␍␊ 50 mV␍␊
256+
* ACV 750 V␍␊500 V␍␊ 50 V␍␊5 V␍␊500 mV␍␊
257+
* DCI: 10 A␍␊5 A␍␊ 500 mA␍␊50 mA␍␊5 mA␍␊500 uA␍␊
258+
* ACI: 10 A␍␊5 A␍␊500 mA␍␊50 mA␍␊5 mA␍␊500 uA␍␊
259+
* RES: 50 M<0xce><0xa9>␍␊5 M<0xce><0xa9>␍␊500 K<0xce><0xa9>␍␊50 K<0xce><0xa9>␍␊5 K<0xce><0xa9>␍␊500 <0xce><0xa9>␍␊500 <0xce><0xa9>␍␊500 <0xce><0xa9>␍␊
260+
* CAP: 50 mF␍␊5 mF␍␊500uF␍␊50uF␍␊5uF␍␊500 nF␍␊50 nF␍␊
261+
* Freq: Hz␍␊
262+
* Period: s␍␊
263+
* Temp: KITS90␍␊Pt100␍␊
264+
*/
265+
SR_PRIV const char *scpi_dmm_owon_get_range_text(const struct sr_dev_inst *sdi)
266+
{
267+
struct dev_context *devc;
268+
int ret;
269+
const struct mqopt_item *mqitem;
270+
gboolean is_auto;
271+
char *response, *pos;
272+
273+
devc = sdi->priv;
274+
275+
ret = scpi_dmm_get_mq(sdi, NULL, NULL, NULL, &mqitem);
276+
if (ret != SR_OK)
277+
return NULL;
278+
if (!mqitem || !mqitem->scpi_func_setup)
279+
return NULL;
280+
if (mqitem->drv_flags & FLAG_NO_RANGE)
281+
return NULL;
282+
283+
scpi_dmm_cmd_delay(sdi->conn);
284+
ret = sr_scpi_cmd(sdi, devc->cmdset, 0, NULL,
285+
DMM_CMD_QUERY_RANGE_AUTO, mqitem->scpi_func_setup);
286+
if (ret != SR_OK)
287+
return NULL;
288+
ret = sr_scpi_get_bool(sdi->conn, NULL, &is_auto);
289+
if (ret != SR_OK)
290+
return NULL;
291+
if (is_auto)
292+
return "auto";
293+
294+
scpi_dmm_cmd_delay(sdi->conn);
295+
ret = sr_scpi_cmd(sdi, devc->cmdset, 0, NULL,
296+
DMM_CMD_QUERY_RANGE, mqitem->scpi_func_setup);
297+
if (ret != SR_OK)
298+
return NULL;
299+
response = NULL;
300+
ret = sr_scpi_get_string(sdi->conn, NULL, &response);
301+
if (ret != SR_OK) {
302+
g_free(response);
303+
return NULL;
304+
}
305+
/* Replace Unicode Omega symbol (0xCE 0xA9) with "Ohm". */
306+
GString *response_str = g_string_new(response);
307+
g_string_replace(response_str, "\xCE\xA9", "Ohm", 0);
308+
g_free(response);
309+
response = g_string_free(response_str, FALSE);
310+
311+
/* Check if space is needed between number and units. */
312+
pos = response;
313+
314+
/* Skip leading whitespace. */
315+
while (*pos && g_ascii_isspace(*pos))
316+
pos++;
317+
318+
/* Find where the number ends. */
319+
char *number_end = pos;
320+
while (*number_end && (g_ascii_isdigit(*number_end) || *number_end == '.' ||
321+
*number_end == '+' || *number_end == '-' || *number_end == 'e' ||
322+
*number_end == 'E'))
323+
number_end++;
324+
325+
/* Check if we found a number and there's a unit character immediately after (no space). */
326+
if (number_end > pos && *number_end && !g_ascii_isspace(*number_end)) {
327+
/* Need to insert space between number and units. */
328+
size_t number_len = number_end - pos;
329+
snprintf(devc->range_text, sizeof(devc->range_text), "%.*s %s",
330+
(int)number_len, pos, number_end);
331+
g_free(response);
332+
return devc->range_text;
333+
} else {
334+
/* Response is fine as is, just copy it. */
335+
snprintf(devc->range_text, sizeof(devc->range_text), "%s", response);
336+
g_free(response);
337+
return devc->range_text;
338+
}
339+
}
340+
219341
SR_PRIV int scpi_dmm_set_range_from_text(const struct sr_dev_inst *sdi,
220342
const char *range)
221343
{
@@ -249,6 +371,107 @@ SR_PRIV int scpi_dmm_set_range_from_text(const struct sr_dev_inst *sdi,
249371
return SR_OK;
250372
}
251373

374+
/* OWON XDM DMMs have two different methods to set range:
375+
* "CONF:VOLT 0.05" will set the range to 50 mV (note the absence of units)
376+
* "RANGE 5" will set the range to fifth option in a list of possible ranges for current measurement mode
377+
* Although the second one would be easier to implement, the first one should not be affected by future changes in firmware
378+
*/
379+
SR_PRIV int scpi_dmm_owon_set_range_from_text(const struct sr_dev_inst *sdi,
380+
const char *range)
381+
{
382+
struct dev_context *devc;
383+
int ret;
384+
const struct mqopt_item *item;
385+
gboolean is_auto;
386+
char processed_range[64];
387+
388+
devc = sdi->priv;
389+
390+
if (!range || !*range)
391+
return SR_ERR_ARG;
392+
393+
ret = scpi_dmm_get_mq(sdi, NULL, NULL, NULL, &item);
394+
if (ret != SR_OK)
395+
return ret;
396+
if (!item || !item->scpi_func_setup)
397+
return SR_ERR_ARG;
398+
if (item->drv_flags & FLAG_NO_RANGE)
399+
return SR_ERR_NA;
400+
401+
is_auto = g_ascii_strcasecmp(range, "auto") == 0;
402+
403+
/* Preprocess range text to handle SI prefixes */
404+
const char *space_pos = strchr(range, ' ');
405+
if (space_pos && *(space_pos + 1)) {
406+
/* Extract the numeric part */
407+
size_t num_len = space_pos - range;
408+
char num_str[32];
409+
strncpy(num_str, range, num_len);
410+
num_str[num_len] = '\0';
411+
412+
/* Parse the numeric value */
413+
double value;
414+
ret = sr_atod_ascii(num_str, &value);
415+
if (ret == SR_OK) {
416+
/* Check for SI prefix after the space */
417+
char prefix = *(space_pos + 1);
418+
double multiplier = 1.0;
419+
420+
switch (prefix) {
421+
case 'k':
422+
case 'K':
423+
multiplier = 1e3;
424+
break;
425+
case 'm':
426+
multiplier = 1e-3;
427+
break;
428+
case 'u':
429+
multiplier = 1e-6;
430+
break;
431+
case 'n':
432+
multiplier = 1e-9;
433+
break;
434+
case 'p':
435+
multiplier = 1e-12;
436+
break;
437+
case 'M':
438+
multiplier = 1e6;
439+
break;
440+
case 'G':
441+
multiplier = 1e9;
442+
break;
443+
default:
444+
/* No recognized SI prefix, use original range */
445+
snprintf(processed_range, sizeof(processed_range), "%s", range);
446+
multiplier = 0.0; /* Flag to use original range */
447+
break;
448+
}
449+
450+
if (multiplier != 0.0) {
451+
/* Apply the multiplier and format the result with locale-independent formatting */
452+
value *= multiplier;
453+
g_ascii_dtostr(processed_range, sizeof(processed_range), value);
454+
}
455+
} else {
456+
/* Failed to parse number, use original range */
457+
snprintf(processed_range, sizeof(processed_range), "%s", range);
458+
}
459+
} else {
460+
/* No space found, use original range */
461+
snprintf(processed_range, sizeof(processed_range), "%s", range);
462+
}
463+
464+
scpi_dmm_cmd_delay(sdi->conn);
465+
ret = sr_scpi_cmd(sdi, devc->cmdset, 0, NULL, DMM_CMD_SETUP_RANGE,
466+
item->scpi_func_setup, is_auto ? "AUTO" : processed_range);
467+
if (ret != SR_OK)
468+
return ret;
469+
if (item->drv_flags & FLAG_CONF_DELAY)
470+
g_usleep(devc->model->conf_delay_us);
471+
472+
return SR_OK;
473+
}
474+
252475
SR_PRIV GVariant *scpi_dmm_get_range_text_list(const struct sr_dev_inst *sdi)
253476
{
254477
GVariantBuilder gvb;
@@ -269,6 +492,75 @@ SR_PRIV GVariant *scpi_dmm_get_range_text_list(const struct sr_dev_inst *sdi)
269492
return list;
270493
}
271494

495+
SR_PRIV GVariant *scpi_dmm_owon_get_range_text_list(const struct sr_dev_inst *sdi)
496+
{
497+
GVariantBuilder gvb;
498+
GVariant *list;
499+
int ret;
500+
enum sr_mq mq;
501+
enum sr_mqflag mqflag;
502+
const char **ranges = NULL;
503+
const struct mqopt_item *mqitem;
504+
int i;
505+
506+
/* Explicitly use string array type, otherwise empty array won't be typed */
507+
g_variant_builder_init(&gvb, G_VARIANT_TYPE_STRING_ARRAY);
508+
509+
/* Get current measurement quantity to return appropriate ranges */
510+
ret = scpi_dmm_get_mq(sdi, &mq, &mqflag, NULL, &mqitem);
511+
if (ret != SR_OK) {
512+
/* Return empty list if we can't determine current mode */
513+
list = g_variant_builder_end(&gvb);
514+
return list;
515+
}
516+
517+
/* Check if current mode has no range support */
518+
if (mqitem && (mqitem->drv_flags & FLAG_NO_RANGE)) {
519+
/* Return empty list for modes that don't support ranges */
520+
list = g_variant_builder_end(&gvb);
521+
return list;
522+
}
523+
/* Select appropriate range array based on current measurement type */
524+
switch (mq) {
525+
case SR_MQ_VOLTAGE:
526+
if (mqflag & SR_MQFLAG_DC) {
527+
ranges = owon_dcv_ranges;
528+
} else if (mqflag & SR_MQFLAG_AC) {
529+
ranges = owon_acv_ranges;
530+
}
531+
break;
532+
case SR_MQ_CURRENT:
533+
if (mqflag & SR_MQFLAG_DC) {
534+
ranges = owon_dci_ranges;
535+
} else if (mqflag & SR_MQFLAG_AC) {
536+
ranges = owon_aci_ranges;
537+
}
538+
break;
539+
case SR_MQ_RESISTANCE:
540+
ranges = owon_res_ranges;
541+
break;
542+
case SR_MQ_CAPACITANCE:
543+
ranges = owon_cap_ranges;
544+
break;
545+
case SR_MQ_TEMPERATURE:
546+
ranges = owon_temp_ranges;
547+
break;
548+
default:
549+
/* For other modes, just provide auto */
550+
break;
551+
}
552+
553+
/* Add all ranges from the selected array */
554+
if (ranges) {
555+
for (i = 0; ranges[i] != NULL; i++) {
556+
g_variant_builder_add(&gvb, "s", ranges[i]);
557+
}
558+
}
559+
560+
list = g_variant_builder_end(&gvb);
561+
return list;
562+
}
563+
272564
SR_PRIV int scpi_dmm_get_meas_agilent(const struct sr_dev_inst *sdi, size_t ch)
273565
{
274566
struct sr_scpi_dev_inst *scpi;

src/hardware/scpi-dmm/protocol.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,13 @@ SR_PRIV int scpi_dmm_get_mq(const struct sr_dev_inst *sdi,
116116
SR_PRIV int scpi_dmm_set_mq(const struct sr_dev_inst *sdi,
117117
enum sr_mq mq, enum sr_mqflag flag);
118118
SR_PRIV const char *scpi_dmm_get_range_text(const struct sr_dev_inst *sdi);
119+
SR_PRIV const char *scpi_dmm_owon_get_range_text(const struct sr_dev_inst *sdi);
119120
SR_PRIV int scpi_dmm_set_range_from_text(const struct sr_dev_inst *sdi,
120121
const char *range);
122+
SR_PRIV int scpi_dmm_owon_set_range_from_text(const struct sr_dev_inst *sdi,
123+
const char *range);
121124
SR_PRIV GVariant *scpi_dmm_get_range_text_list(const struct sr_dev_inst *sdi);
125+
SR_PRIV GVariant *scpi_dmm_owon_get_range_text_list(const struct sr_dev_inst *sdi);
122126
SR_PRIV int scpi_dmm_get_meas_agilent(const struct sr_dev_inst *sdi, size_t ch);
123127
SR_PRIV int scpi_dmm_get_meas_gwinstek(const struct sr_dev_inst *sdi, size_t ch);
124128
SR_PRIV int scpi_dmm_receive_data(int fd, int revents, void *cb_data);

0 commit comments

Comments
 (0)