]> sigrok.org Git - libsigrok.git/blob - src/dmm/mm38xr.c
output/csv: use intermediate time_t var, silence compiler warning
[libsigrok.git] / src / dmm / mm38xr.c
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
58 enum 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
77 enum 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
98 enum mm38xr_adcd_mode {
99         ACDC_MODE_NONE = 1000,
100         ACDC_MODE_DC,
101         ACDC_MODE_AC,
102         ACDC_MODE_AC_AND_DC,
103 };
104
105 struct 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
120 static 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
138 static 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 */
157 static uint32_t meterman_38xr_hexnibble_to_uint(uint8_t v)
158 {
159         return (v <= '9') ? v - '0' : v - 'A' + 10;
160 }
161
162 static 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
171 static 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
180 static 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
198 static 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
208 static 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
218 static 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
316 SR_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
340 SR_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;
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
352         digits = 0;
353
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) {
444                 digits = units_exponents[mi.meas_mode][mi.rangecode] -
445                         decimal_digits[mi.meas_mode][mi.rangecode];
446
447                 *floatval = mi.reading;
448                 if (meterman_38xr_is_negative(&mi)) {
449                         *floatval *= -1.0f;
450                 }
451                 *floatval *= powf(10, digits);
452         }
453         analog->encoding->digits = -digits;
454         analog->spec->spec_digits = -digits;
455
456         return SR_OK;
457 }