]> sigrok.org Git - libsigrok.git/blame - src/dmm/mm38xr.c
output/csv: use intermediate time_t var, silence compiler warning
[libsigrok.git] / src / dmm / mm38xr.c
CommitLineData
4c29bba1
PS
1/*
2 * This file is part of the libsigrok project.
3 *
4 * Copyright (C) 2020 Peter Skarpetis <peters@skarpetis.com>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/*
21 * Meterman 38XR protocol parser
22 *
23 * Communication parameters: Unidirectional, 9600/8n1
24 *
25 * The user guide can be downloaded from:
26 * https://assets.tequipment.net/assets/1/26/Documents/38XR_Manual.pdf
27 *
28 * Protocol is described in a PDF available at:
29 * https://www.elfadistrelec.fi/Web/Downloads/od/es/fj38XR-Serial-Output-Codes.pdf
30 *
31 * There is also a disussion about the protocol at the NI forum:
32 * https://forums.ni.com/t5/Digital-Multimeters-DMMs-and/Meterman-DMM/td-p/179597?profile.language=en
33 *
34 * EEVBlog discussion thread about the meter
35 * https://www.eevblog.com/forum/chat/meterman-38xr/
36 */
37
38/**
39 * @file
40 *
41 * Meterman 38XR ASCII protocol parser.
42 */
43
44#include <config.h>
45
46#include <glib.h>
47#include <libsigrok/libsigrok.h>
48#include "libsigrok-internal.h"
49#include <math.h>
50#include <string.h>
51
52#define LOG_PREFIX "mm38xr"
53
54#define METERMAN_DIGITS_OVERLOAD 0xb0dd
55#define METERMAN_DIGITS_BAD_INPUT_JACK 0xbaab
56#define METERMAN_BARGRAPH_NO_SEGMENTS = 0x2a
57
58enum mm38xr_func_code {
59 FUNC_CODE_UNUSED = 0x01,
60 FUNC_CODE_TEMPERATURE_FARENHEIGHT = 0x02,
61 FUNC_CODE_CURRENT_4_20_MAMPS = 0x03, /* 4-20 mA */
62 FUNC_CODE_DIODE_TEST = 0x04,
63 FUNC_CODE_INDUCTANCE_HENRIES = 0x05,
64 FUNC_CODE_TEMPERATURE_CELSIUS = 0x06,
65 FUNC_CODE_CURRENT_UAMPS = 0x07, /* uA */
66 FUNC_CODE_RESISTANCE_OHMS = 0x08,
67 FUNC_CODE_INDUCTANCE_MHENRIES = 0x09, /* mH */
68 FUNC_CODE_CURRENT_10_AMPS = 0x0a,
69 FUNC_CODE_CAPACITANCE = 0x0b,
70 FUNC_CODE_VOLTS_DC = 0x0c,
71 FUNC_CODE_LOGIC = 0x0d,
72 FUNC_CODE_CURRENT_MAMPS = 0x0e, /* mA */
73 FUNC_CODE_FREQUENCY_HZ = 0x0f, /* and duty cycle */
74 FUNC_CODE_VOLTS_AC = 0x10, /* and dBm */
75};
76
77enum mm38xr_meas_mode {
78 /* This is used to index into the digits and exponent arrays below. */
79 MEAS_MODE_VOLTS,
80 MEAS_MODE_RESISTANCE_OHMS,
81 MEAS_MODE_CURRENT_UAMPS, /* uA */
82 MEAS_MODE_CURRENT_MAMPS, /* mA */
83 MEAS_MODE_CURRENT_AMPS,
84 MEAS_MODE_CAPACITANCE,
85 MEAS_MODE_DIODE_TEST,
86 MEAS_MODE_TEMPERATURE_C,
87 MEAS_MODE_TEMPERATURE_F,
88 MEAS_MODE_FREQUENCY_HZ,
89 MEAS_MODE_INDUCTANCE_H,
90 MEAS_MODE_INDUCTANCE_MH, /* mH */
91 MEAS_MODE_DBM,
92 MEAS_MODE_DUTY_CYCLE,
93 MEAS_MODE_CONTINUITY,
94 /* For internal purposes. */
95 MEAS_MODE_UNDEFINED,
96};
97
98enum mm38xr_adcd_mode {
99 ACDC_MODE_NONE = 1000,
100 ACDC_MODE_DC,
101 ACDC_MODE_AC,
102 ACDC_MODE_AC_AND_DC,
103};
104
105struct meterman_info {
106 enum mm38xr_func_code functioncode; /* columns 0, 1 */
107 unsigned int reading; /* columns 2,3,4,5; LCD digits */
108 unsigned int bargraphsegments; /* columns 6, 7; max 40 segments, 0x2A = no bargraph */
109 size_t rangecode; /* column 8 */
110 unsigned int ampsfunction; /* column 9 */
111 unsigned int peakstatus; /* column 10 */
112 unsigned int rflag_h; /* column 11 */
113 unsigned int rflag_l; /* column 12 */
114
115 /* calculated values */
116 enum mm38xr_meas_mode meas_mode;
117 enum mm38xr_adcd_mode acdc;
118};
119
120static const int decimal_digits[][7] = {
121 [MEAS_MODE_VOLTS] = { 1, 3, 2, 1, 0, 0, 0, },
122 [MEAS_MODE_RESISTANCE_OHMS] = { 2, 3, 4, 2, 3, 1, 0, },
123 [MEAS_MODE_CURRENT_UAMPS] = { 2, 1, 0, 0, 0, 0, 0, },
124 [MEAS_MODE_CURRENT_MAMPS] = { 3, 2, 1, 0, 0, 0, 0, },
125 [MEAS_MODE_CURRENT_AMPS] = { 3, 0, 0, 0, 0, 0, 0, },
126 [MEAS_MODE_CAPACITANCE] = { 2, 1, 3, 2, 1, 0, 0, },
127 [MEAS_MODE_DIODE_TEST] = { 0, 3, 0, 0, 0, 0, 0, },
128 [MEAS_MODE_TEMPERATURE_C] = { 0, 0, 0, 0, 0, 0, 0, },
129 [MEAS_MODE_TEMPERATURE_F] = { 0, 0, 0, 0, 0, 0, 0, },
130 [MEAS_MODE_FREQUENCY_HZ] = { 2, 1, 3, 2, 1, 3, 2, },
131 [MEAS_MODE_INDUCTANCE_H] = { 0, 0, 0, 3, 2, 0, 0, },
132 [MEAS_MODE_INDUCTANCE_MH] = { 3, 2, 1, 0, 0, 0, 0, },
133 [MEAS_MODE_DBM] = { 2, 2, 2, 2, 2, 2, 2, },
134 [MEAS_MODE_DUTY_CYCLE] = { 2, 2, 2, 2, 2, 2, 2, },
135 [MEAS_MODE_CONTINUITY] = { 0, 0, 0, 0, 0, 1, 0, },
136};
137
138static const int units_exponents[][7] = {
139 [MEAS_MODE_VOLTS] = { -3, 0, 0, 0, 0, 0, 0, },
140 [MEAS_MODE_RESISTANCE_OHMS] = { 6, 6, 6, 3, 3, 0, 0, },
141 [MEAS_MODE_CURRENT_UAMPS] = { -6, -6, 0, 0, 0, 0, 0, },
142 [MEAS_MODE_CURRENT_MAMPS] = { -3, -3, -3, 0, 0, 0, 0, },
143 [MEAS_MODE_CURRENT_AMPS] = { 0, 0, 0, 0, 0, 0, 0, },
144 [MEAS_MODE_CAPACITANCE] = { -9, -9, -6, -6, -6, 0, 0, },
145 [MEAS_MODE_DIODE_TEST] = { 0, 0, 0, 0, 0, 0, 0, },
146 [MEAS_MODE_TEMPERATURE_C] = { 0, 0, 0, 0, 0, 0, 0, },
147 [MEAS_MODE_TEMPERATURE_F] = { 0, 0, 0, 0, 0, 0, 0, },
148 [MEAS_MODE_FREQUENCY_HZ] = { 0, 0, 3, 3, 3, 6, 6, },
149 [MEAS_MODE_INDUCTANCE_H] = { 0, 0, 0, 0, 0, 0, 0, },
150 [MEAS_MODE_INDUCTANCE_MH] = { -3, -3, -3, 0, 0, 0, 0, },
151 [MEAS_MODE_DBM] = { 0, 0, 0, 0, 0, 0, 0, },
152 [MEAS_MODE_DUTY_CYCLE] = { 0, 0, 0, 0, 0, 0, 0, },
153 [MEAS_MODE_CONTINUITY] = { 0, 0, 0, 0, 0, 0, 0, },
154};
155
156/* Assumes caller has already checked data fall within 0..9 and A..F */
157static uint32_t meterman_38xr_hexnibble_to_uint(uint8_t v)
158{
159 return (v <= '9') ? v - '0' : v - 'A' + 10;
160}
161
162static uint32_t meterman_38xr_func_code(const uint8_t *buf)
163{
164 uint32_t v;
165
166 v = meterman_38xr_hexnibble_to_uint(buf[0]) << 4 |
167 meterman_38xr_hexnibble_to_uint(buf[1]);
168 return v;
169}
170
171static uint32_t meterman_38xr_barsegments(const uint8_t *buf)
172{
173 uint32_t v;
174
175 v = meterman_38xr_hexnibble_to_uint(buf[6]) << 4 |
176 meterman_38xr_hexnibble_to_uint(buf[7]);
177 return v;
178}
179
180static uint32_t meterman_38xr_reading(const uint8_t *buf)
181{
182 uint32_t v;
183
184 if (buf[2] > 'A') { /* overload */
185 v = meterman_38xr_hexnibble_to_uint(buf[2]) << 12 |
186 meterman_38xr_hexnibble_to_uint(buf[3]) << 8 |
187 meterman_38xr_hexnibble_to_uint(buf[4]) << 4 |
188 meterman_38xr_hexnibble_to_uint(buf[5]) << 0;
189 } else {
190 v = meterman_38xr_hexnibble_to_uint(buf[2]) * 1000 +
191 meterman_38xr_hexnibble_to_uint(buf[3]) * 100 +
192 meterman_38xr_hexnibble_to_uint(buf[4]) * 10 +
193 meterman_38xr_hexnibble_to_uint(buf[5]) * 1;
194 }
195 return v;
196}
197
198static gboolean meterman_38xr_is_negative(struct meterman_info *mi)
199{
200
201 if (mi->rflag_l == 0x01)
202 return TRUE;
203 if (mi->meas_mode == MEAS_MODE_DBM && mi->rflag_l == 0x05)
204 return TRUE;
205 return FALSE;
206}
207
208static int currentACDC(struct meterman_info *mi)
209{
210
211 if (mi->ampsfunction == 0x01)
212 return ACDC_MODE_AC;
213 if (mi->ampsfunction == 0x02)
214 return ACDC_MODE_AC_AND_DC;
215 return ACDC_MODE_DC;
216}
217
218static int meterman_38xr_decode(const uint8_t *buf, struct meterman_info *mi)
219{
220
221 if (!meterman_38xr_packet_valid(buf))
222 return SR_ERR;
223
224 mi->functioncode = meterman_38xr_func_code(buf);
225 if (mi->functioncode < 2 || mi->functioncode > 0x10)
226 return SR_ERR;
227 mi->reading = meterman_38xr_reading(buf);
228 mi->bargraphsegments = meterman_38xr_barsegments(buf);
229 mi->rangecode = meterman_38xr_hexnibble_to_uint(buf[8]);
230 if (mi->rangecode > 6)
231 return SR_ERR;
232 mi->ampsfunction = meterman_38xr_hexnibble_to_uint(buf[9]);
233 mi->peakstatus = meterman_38xr_hexnibble_to_uint(buf[10]);
234 mi->rflag_h = meterman_38xr_hexnibble_to_uint(buf[11]);
235 mi->rflag_l = meterman_38xr_hexnibble_to_uint(buf[12]);
236
237 mi->acdc = ACDC_MODE_NONE;
238 switch (mi->functioncode) {
239 case FUNC_CODE_TEMPERATURE_FARENHEIGHT:
240 mi->meas_mode = MEAS_MODE_TEMPERATURE_F;
241 break;
242
243 case FUNC_CODE_CURRENT_4_20_MAMPS:
244 mi->meas_mode = MEAS_MODE_CURRENT_MAMPS;
245 mi->acdc = currentACDC(mi);
246 break;
247
248 case FUNC_CODE_DIODE_TEST:
249 mi->meas_mode = MEAS_MODE_DIODE_TEST;
250 mi->acdc = ACDC_MODE_DC;
251 break;
252
253 case FUNC_CODE_INDUCTANCE_HENRIES:
254 mi->meas_mode = MEAS_MODE_INDUCTANCE_H;
255 break;
256
257 case FUNC_CODE_TEMPERATURE_CELSIUS:
258 mi->meas_mode = MEAS_MODE_TEMPERATURE_C;
259 break;
260
261 case FUNC_CODE_CURRENT_UAMPS:
262 mi->meas_mode = MEAS_MODE_CURRENT_UAMPS;
263 mi->acdc = currentACDC(mi);
264 break;
265
266 case FUNC_CODE_RESISTANCE_OHMS:
267 mi->meas_mode = (mi->rflag_l == 0x08)
268 ? MEAS_MODE_CONTINUITY
269 : MEAS_MODE_RESISTANCE_OHMS;
270 break;
271
272 case FUNC_CODE_INDUCTANCE_MHENRIES:
273 mi->meas_mode = MEAS_MODE_INDUCTANCE_MH;
274 break;
275
276 case FUNC_CODE_CURRENT_10_AMPS:
277 mi->meas_mode = MEAS_MODE_CURRENT_AMPS;
278 mi->acdc = currentACDC(mi);
279 break;
280
281 case FUNC_CODE_CAPACITANCE:
282 mi->meas_mode = MEAS_MODE_CAPACITANCE;
283 break;
284
285 case FUNC_CODE_VOLTS_DC:
286 mi->meas_mode = MEAS_MODE_VOLTS;
287 mi->acdc = (mi->rflag_l == 0x02)
288 ? ACDC_MODE_AC_AND_DC : ACDC_MODE_DC;
289 break;
290
291 case FUNC_CODE_CURRENT_MAMPS:
292 mi->meas_mode = MEAS_MODE_CURRENT_MAMPS;
293 mi->acdc = currentACDC(mi);
294 break;
295
296 case FUNC_CODE_FREQUENCY_HZ:
297 mi->meas_mode = (mi->rflag_h == 0x0B)
298 ? MEAS_MODE_DUTY_CYCLE
299 : MEAS_MODE_FREQUENCY_HZ;
300 break;
301
302 case FUNC_CODE_VOLTS_AC:
303 mi->meas_mode = (mi->rflag_l == 0x04 || mi->rflag_l == 0x05)
304 ? MEAS_MODE_DBM : MEAS_MODE_VOLTS;
305 mi->acdc = ACDC_MODE_AC;
306 break;
307
308 default:
309 mi->meas_mode = MEAS_MODE_UNDEFINED;
310 return SR_ERR;
311
312 }
313 return SR_OK;
314}
315
316SR_PRIV gboolean meterman_38xr_packet_valid(const uint8_t *buf)
317{
318 size_t i;
319 uint32_t fcode;
320
321 if ((buf[13] != '\r') || (buf[14] != '\n'))
322 return FALSE;
323
324 /* Check for all hex digits */
325 for (i = 0; i < 13; i++) {
326 if (buf[i] < '0')
327 return FALSE;
328 if (buf[i] > '9' && buf[i] < 'A')
329 return FALSE;
330 if (buf[i] > 'F')
331 return FALSE;
332 }
333 fcode = meterman_38xr_func_code(buf);
334 if (fcode < 0x01 || fcode > 0x10)
335 return FALSE;
336
337 return TRUE;
338}
339
340SR_PRIV int meterman_38xr_parse(const uint8_t *buf, float *floatval,
341 struct sr_datafeed_analog *analog, void *info)
342{
343 gboolean is_overload, is_bad_jack;
4c29bba1
PS
344 int digits;
345 struct meterman_info mi;
346
347 (void)info;
348
349 if (meterman_38xr_decode(buf, &mi) != SR_OK)
350 return SR_ERR;
351
e59dd45d
FS
352 digits = 0;
353
4c29bba1
PS
354 if (mi.meas_mode != MEAS_MODE_CONTINUITY) {
355 is_overload = mi.reading == METERMAN_DIGITS_OVERLOAD;
356 is_bad_jack = mi.reading == METERMAN_DIGITS_BAD_INPUT_JACK;
357 if (is_overload || is_bad_jack) {
358 sr_spew("Over limit.");
359 *floatval = INFINITY; /* overload */
360 return SR_OK;
361 }
362 }
363 switch (mi.meas_mode) {
364 case MEAS_MODE_VOLTS:
365 analog->meaning->mq = SR_MQ_VOLTAGE;
366 analog->meaning->unit = SR_UNIT_VOLT;
367 break;
368 case MEAS_MODE_RESISTANCE_OHMS:
369 analog->meaning->mq = SR_MQ_RESISTANCE;
370 analog->meaning->unit = SR_UNIT_OHM;
371 break;
372 case MEAS_MODE_CURRENT_UAMPS:
373 case MEAS_MODE_CURRENT_MAMPS:
374 case MEAS_MODE_CURRENT_AMPS:
375 analog->meaning->mq = SR_MQ_CURRENT;
376 analog->meaning->unit = SR_UNIT_AMPERE;
377 break;
378 case MEAS_MODE_CAPACITANCE:
379 analog->meaning->mq = SR_MQ_CAPACITANCE;
380 analog->meaning->unit = SR_UNIT_FARAD;
381 break;
382 case MEAS_MODE_DIODE_TEST:
383 analog->meaning->mq = SR_MQ_VOLTAGE;
384 analog->meaning->unit = SR_UNIT_VOLT;
385 analog->meaning->mqflags |= SR_MQFLAG_DIODE;
386 break;
387 case MEAS_MODE_TEMPERATURE_C:
388 analog->meaning->mq = SR_MQ_TEMPERATURE;
389 analog->meaning->unit = SR_UNIT_CELSIUS;
390 break;
391 case MEAS_MODE_TEMPERATURE_F:
392 analog->meaning->mq = SR_MQ_TEMPERATURE;
393 analog->meaning->unit = SR_UNIT_FAHRENHEIT;
394 break;
395 case MEAS_MODE_FREQUENCY_HZ:
396 analog->meaning->mq = SR_MQ_FREQUENCY;
397 analog->meaning->unit = SR_UNIT_HERTZ;
398 break;
399 case MEAS_MODE_INDUCTANCE_H:
400 analog->meaning->mq = SR_MQ_SERIES_INDUCTANCE;
401 analog->meaning->unit = SR_UNIT_HENRY;
402 break;
403 case MEAS_MODE_INDUCTANCE_MH:
404 analog->meaning->mq = SR_MQ_SERIES_INDUCTANCE;
405 analog->meaning->unit = SR_UNIT_HENRY;
406 break;
407 case MEAS_MODE_DBM:
408 analog->meaning->mq = SR_MQ_VOLTAGE;
409 analog->meaning->unit = SR_UNIT_DECIBEL_MW;
410 analog->meaning->mqflags |= SR_MQFLAG_AC;
411 break;
412 case MEAS_MODE_DUTY_CYCLE:
413 analog->meaning->mq = SR_MQ_DUTY_CYCLE;
414 analog->meaning->unit = SR_UNIT_PERCENTAGE;
415 break;
416 case MEAS_MODE_CONTINUITY:
417 analog->meaning->mq = SR_MQ_CONTINUITY;
418 analog->meaning->unit = SR_UNIT_BOOLEAN;
419 *floatval = (mi.reading == METERMAN_DIGITS_OVERLOAD) ? 0.0 : 1.0;
420 break;
421 default:
422 return SR_ERR;
423 }
424 switch (mi.acdc) {
425 case ACDC_MODE_DC:
426 analog->meaning->mqflags |= SR_MQFLAG_DC;
427 break;
428 case ACDC_MODE_AC:
429 analog->meaning->mqflags |= SR_MQFLAG_AC;
430 break;
431 case ACDC_MODE_AC_AND_DC:
432 analog->meaning->mqflags |= SR_MQFLAG_DC | SR_MQFLAG_AC;
433 break;
434 default:
435 break;
436 }
437 if (mi.peakstatus == 0x02 || mi.peakstatus == 0x0a)
438 analog->meaning->mqflags |= SR_MQFLAG_MAX;
439 if (mi.peakstatus == 0x03 || mi.peakstatus == 0x0b)
440 analog->meaning->mqflags |= SR_MQFLAG_MIN;
441 if (mi.rflag_h == 0x0a || mi.peakstatus == 0x0b)
442 analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE;
443 if (mi.meas_mode != MEAS_MODE_CONTINUITY) {
e59dd45d
FS
444 digits = units_exponents[mi.meas_mode][mi.rangecode] -
445 decimal_digits[mi.meas_mode][mi.rangecode];
4c29bba1
PS
446
447 *floatval = mi.reading;
448 if (meterman_38xr_is_negative(&mi)) {
449 *floatval *= -1.0f;
450 }
e59dd45d 451 *floatval *= powf(10, digits);
4c29bba1 452 }
e59dd45d
FS
453 analog->encoding->digits = -digits;
454 analog->spec->spec_digits = -digits;
4c29bba1
PS
455
456 return SR_OK;
457}