]>
Commit | Line | Data |
---|---|---|
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 | } |