]> sigrok.org Git - libsigrok.git/blob - src/dmm/rs9lcd.c
output/csv: use intermediate time_t var, silence compiler warning
[libsigrok.git] / src / dmm / rs9lcd.c
1 /*
2  * This file is part of the libsigrok project.
3  *
4  * Copyright (C) 2012 Alexandru Gagniuc <mr.nuke.me@gmail.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  * RadioShack 22-812 protocol parser.
22  *
23  * This protocol is currently encountered on the RadioShack 22-812 DMM.
24  * It is a 9-byte packet representing a 1:1 mapping of the LCD segments, hence
25  * the name rs9lcd.
26  *
27  * The chip is a bare die covered by a plastic blob. It is unclear if this chip
28  * and protocol is used on any other device.
29  */
30
31 #include <config.h>
32 #include <string.h>
33 #include <ctype.h>
34 #include <math.h>
35 #include <glib.h>
36 #include <libsigrok/libsigrok.h>
37 #include "libsigrok-internal.h"
38
39 #define LOG_PREFIX "rs9lcd"
40
41 /* Byte 1 of the packet, and the modes it represents */
42 #define IND1_HZ         (1 << 7)
43 #define IND1_OHM        (1 << 6)
44 #define IND1_KILO       (1 << 5)
45 #define IND1_MEGA       (1 << 4)
46 #define IND1_FARAD      (1 << 3)
47 #define IND1_AMP        (1 << 2)
48 #define IND1_VOLT       (1 << 1)
49 #define IND1_MILI       (1 << 0)
50 /* Byte 2 of the packet, and the modes it represents */
51 #define IND2_MICRO      (1 << 7)
52 #define IND2_NANO       (1 << 6)
53 #define IND2_DBM        (1 << 5)
54 #define IND2_SEC        (1 << 4)
55 #define IND2_DUTY       (1 << 3)
56 #define IND2_HFE        (1 << 2)
57 #define IND2_REL        (1 << 1)
58 #define IND2_MIN        (1 << 0)
59 /* Byte 7 of the packet, and the modes it represents */
60 #define INFO_BEEP       (1 << 7)
61 #define INFO_DIODE      (1 << 6)
62 #define INFO_BAT        (1 << 5)
63 #define INFO_HOLD       (1 << 4)
64 #define INFO_NEG        (1 << 3)
65 #define INFO_AC         (1 << 2)
66 #define INFO_RS232      (1 << 1)
67 #define INFO_AUTO       (1 << 0)
68 /* Instead of a decimal point, digit 4 carries the MAX flag */
69 #define DIG4_MAX        (1 << 3)
70 /* Mask to remove the decimal point from a digit */
71 #define DP_MASK         (1 << 3)
72
73 /* What the LCD values represent */
74 #define LCD_0           0xd7
75 #define LCD_1           0x50
76 #define LCD_2           0xb5
77 #define LCD_3           0xf1
78 #define LCD_4           0x72
79 #define LCD_5           0xe3
80 #define LCD_6           0xe7
81 #define LCD_7           0x51
82 #define LCD_8           0xf7
83 #define LCD_9           0xf3
84
85 #define LCD_C           0x87
86 #define LCD_E
87 #define LCD_F
88 #define LCD_h           0x66
89 #define LCD_H           0x76
90 #define LCD_I
91 #define LCD_n
92 #define LCD_P           0x37
93 #define LCD_r
94
95 enum {
96         MODE_DC_V       = 0,
97         MODE_AC_V       = 1,
98         MODE_DC_UA      = 2,
99         MODE_DC_MA      = 3,
100         MODE_DC_A       = 4,
101         MODE_AC_UA      = 5,
102         MODE_AC_MA      = 6,
103         MODE_AC_A       = 7,
104         MODE_OHM        = 8,
105         MODE_FARAD      = 9,
106         MODE_HZ         = 10,
107         MODE_VOLT_HZ    = 11,   /* Dial set to V, Hz selected by Hz button */
108         MODE_AMP_HZ     = 12,   /* Dial set to A, Hz selected by Hz button */
109         MODE_DUTY       = 13,
110         MODE_VOLT_DUTY  = 14,   /* Dial set to V, duty cycle selected */
111         MODE_AMP_DUTY   = 15,   /* Dial set to A, duty cycle selected */
112         MODE_WIDTH      = 16,
113         MODE_VOLT_WIDTH = 17,   /* Dial set to V, pulse width selected */
114         MODE_AMP_WIDTH  = 18,   /* Dial set to A, pulse width selected */
115         MODE_DIODE      = 19,
116         MODE_CONT       = 20,
117         MODE_HFE        = 21,
118         MODE_LOGIC      = 22,
119         MODE_DBM        = 23,
120         /* MODE_EF      = 24, */ /* Not encountered on any DMM */
121         MODE_TEMP       = 25,
122         MODE_INVALID    = 26,
123 };
124
125 enum {
126         READ_ALL,
127         READ_TEMP,
128 };
129
130 struct rs9lcd_packet {
131         uint8_t mode;
132         uint8_t indicatrix1;
133         uint8_t indicatrix2;
134         uint8_t digit4;
135         uint8_t digit3;
136         uint8_t digit2;
137         uint8_t digit1;
138         uint8_t info;
139         uint8_t checksum;
140 };
141
142 static gboolean checksum_valid(const struct rs9lcd_packet *rs_packet)
143 {
144         uint8_t *raw;
145         uint8_t sum = 0;
146         int i;
147
148         raw = (void *)rs_packet;
149
150         for (i = 0; i < RS9LCD_PACKET_SIZE - 1; i++)
151                 sum += raw[i];
152
153         /* This is just a funky constant added to the checksum. */
154         sum += 57;
155         sum -= rs_packet->checksum;
156         return (sum == 0);
157 }
158
159 static gboolean selection_good(const struct rs9lcd_packet *rs_packet)
160 {
161         int count;
162
163         /* Does the packet have more than one multiplier? */
164         count = 0;
165         count += (rs_packet->indicatrix1 & IND1_KILO)  ? 1 : 0;
166         count += (rs_packet->indicatrix1 & IND1_MEGA)  ? 1 : 0;
167         count += (rs_packet->indicatrix1 & IND1_MILI)  ? 1 : 0;
168         count += (rs_packet->indicatrix2 & IND2_MICRO) ? 1 : 0;
169         count += (rs_packet->indicatrix2 & IND2_NANO)  ? 1 : 0;
170         if (count > 1) {
171                 sr_dbg("More than one multiplier detected in packet.");
172                 return FALSE;
173         }
174
175         /* Does the packet "measure" more than one type of value? */
176         count = 0;
177         count += (rs_packet->indicatrix1 & IND1_HZ)    ? 1 : 0;
178         count += (rs_packet->indicatrix1 & IND1_OHM)   ? 1 : 0;
179         count += (rs_packet->indicatrix1 & IND1_FARAD) ? 1 : 0;
180         count += (rs_packet->indicatrix1 & IND1_AMP)   ? 1 : 0;
181         count += (rs_packet->indicatrix1 & IND1_VOLT)  ? 1 : 0;
182         count += (rs_packet->indicatrix2 & IND2_DBM)   ? 1 : 0;
183         count += (rs_packet->indicatrix2 & IND2_SEC)   ? 1 : 0;
184         count += (rs_packet->indicatrix2 & IND2_DUTY)  ? 1 : 0;
185         count += (rs_packet->indicatrix2 & IND2_HFE)   ? 1 : 0;
186         if (count > 1) {
187                 sr_dbg("More than one measurement type detected in packet.");
188                 return FALSE;
189         }
190
191         return TRUE;
192 }
193
194 /*
195  * Since the 22-812 does not identify itself in any way, shape, or form,
196  * we really don't know for sure who is sending the data. We must use every
197  * possible check to filter out bad packets, especially since detection of the
198  * 22-812 depends on how well we can filter the packets.
199  */
200 SR_PRIV gboolean sr_rs9lcd_packet_valid(const uint8_t *buf)
201 {
202         const struct rs9lcd_packet *rs_packet = (void *)buf;
203
204         /*
205          * Check for valid mode first, before calculating the checksum. No
206          * point calculating the checksum, if we know we'll reject the packet.
207          */
208         if (!(rs_packet->mode < MODE_INVALID))
209                 return FALSE;
210
211         if (!checksum_valid(rs_packet)) {
212                 sr_spew("Packet with invalid checksum. Discarding.");
213                 return FALSE;
214         }
215
216         if (!selection_good(rs_packet)) {
217                 sr_spew("Packet with invalid selection bits. Discarding.");
218                 return FALSE;
219         }
220
221         return TRUE;
222 }
223
224 static uint8_t decode_digit(uint8_t raw_digit)
225 {
226         /* Take out the decimal point, so we can use a simple switch(). */
227         raw_digit &= ~DP_MASK;
228
229         switch (raw_digit) {
230         case 0x00:
231         case LCD_0:
232                 return 0;
233         case LCD_1:
234                 return 1;
235         case LCD_2:
236                 return 2;
237         case LCD_3:
238                 return 3;
239         case LCD_4:
240                 return 4;
241         case LCD_5:
242                 return 5;
243         case LCD_6:
244                 return 6;
245         case LCD_7:
246                 return 7;
247         case LCD_8:
248                 return 8;
249         case LCD_9:
250                 return 9;
251         default:
252                 sr_dbg("Invalid digit byte: 0x%02x.", raw_digit);
253                 return 0xff;
254         }
255 }
256
257 static double lcd_to_double(const struct rs9lcd_packet *rs_packet, int type,
258                             int *exponent)
259 {
260         double rawval = 0;
261         uint8_t digit, raw_digit;
262         gboolean dp_reached = FALSE;
263         int i, end;
264
265         *exponent = 0;
266
267         /* end = 1: Don't parse last digit. end = 0: Parse all digits. */
268         end = (type == READ_TEMP) ? 1 : 0;
269
270         /* We have 4 digits, and we start from the most significant. */
271         for (i = 3; i >= end; i--) {
272                 raw_digit = *(&(rs_packet->digit4) + i);
273                 digit = decode_digit(raw_digit);
274                 if (digit == 0xff) {
275                         rawval = NAN;
276                         break;
277                 }
278                 /*
279                  * Digit 1 does not have a decimal point. Instead, the decimal
280                  * point is used to indicate MAX, so we must avoid testing it.
281                  */
282                 if ((i < 3) && (raw_digit & DP_MASK))
283                         dp_reached = TRUE;
284                 if (dp_reached)
285                         *exponent -= 1;
286                 rawval = rawval * 10 + digit;
287         }
288         if (rs_packet->info & INFO_NEG)
289                 rawval *= -1;
290
291         /* See if we need to multiply our raw value by anything. */
292         if (rs_packet->indicatrix2 & IND2_NANO)
293                 *exponent -= 9;
294         else if (rs_packet->indicatrix2 & IND2_MICRO)
295                 *exponent -= 6;
296         else if (rs_packet->indicatrix1 & IND1_MILI)
297                 *exponent -= 3;
298         else if (rs_packet->indicatrix1 & IND1_KILO)
299                 *exponent += 3;
300         else if (rs_packet->indicatrix1 & IND1_MEGA)
301                 *exponent += 6;
302
303         return rawval * powf(10, *exponent);
304 }
305
306 static gboolean is_celsius(const struct rs9lcd_packet *rs_packet)
307 {
308         return ((rs_packet->digit4 & ~DP_MASK) == LCD_C);
309 }
310
311 static gboolean is_shortcirc(const struct rs9lcd_packet *rs_packet)
312 {
313         return ((rs_packet->digit2 & ~DP_MASK) == LCD_h);
314 }
315
316 static gboolean is_logic_high(const struct rs9lcd_packet *rs_packet)
317 {
318         sr_spew("Digit 2: 0x%02x.", rs_packet->digit2 & ~DP_MASK);
319         return ((rs_packet->digit2 & ~DP_MASK) == LCD_H);
320 }
321
322 SR_PRIV int sr_rs9lcd_parse(const uint8_t *buf, float *floatval,
323                             struct sr_datafeed_analog *analog, void *info)
324 {
325         const struct rs9lcd_packet *rs_packet = (void *)buf;
326         int exponent;
327         double rawval;
328
329         (void)info;
330
331         rawval = lcd_to_double(rs_packet, READ_ALL, &exponent);
332
333         switch (rs_packet->mode) {
334         case MODE_DC_V:
335                 analog->meaning->mq = SR_MQ_VOLTAGE;
336                 analog->meaning->unit = SR_UNIT_VOLT;
337                 analog->meaning->mqflags |= SR_MQFLAG_DC;
338                 break;
339         case MODE_AC_V:
340                 analog->meaning->mq = SR_MQ_VOLTAGE;
341                 analog->meaning->unit = SR_UNIT_VOLT;
342                 analog->meaning->mqflags |= SR_MQFLAG_AC;
343                 break;
344         case MODE_DC_UA:        /* Fall through */
345         case MODE_DC_MA:        /* Fall through */
346         case MODE_DC_A:
347                 analog->meaning->mq = SR_MQ_CURRENT;
348                 analog->meaning->unit = SR_UNIT_AMPERE;
349                 analog->meaning->mqflags |= SR_MQFLAG_DC;
350                 break;
351         case MODE_AC_UA:        /* Fall through */
352         case MODE_AC_MA:        /* Fall through */
353         case MODE_AC_A:
354                 analog->meaning->mq = SR_MQ_CURRENT;
355                 analog->meaning->unit = SR_UNIT_AMPERE;
356                 analog->meaning->mqflags |= SR_MQFLAG_AC;
357                 break;
358         case MODE_OHM:
359                 analog->meaning->mq = SR_MQ_RESISTANCE;
360                 analog->meaning->unit = SR_UNIT_OHM;
361                 break;
362         case MODE_FARAD:
363                 analog->meaning->mq = SR_MQ_CAPACITANCE;
364                 analog->meaning->unit = SR_UNIT_FARAD;
365                 break;
366         case MODE_CONT:
367                 analog->meaning->mq = SR_MQ_CONTINUITY;
368                 analog->meaning->unit = SR_UNIT_BOOLEAN;
369                 rawval = is_shortcirc(rs_packet);
370                 break;
371         case MODE_DIODE:
372                 analog->meaning->mq = SR_MQ_VOLTAGE;
373                 analog->meaning->unit = SR_UNIT_VOLT;
374                 analog->meaning->mqflags |= SR_MQFLAG_DIODE | SR_MQFLAG_DC;
375                 break;
376         case MODE_HZ:           /* Fall through */
377         case MODE_VOLT_HZ:      /* Fall through */
378         case MODE_AMP_HZ:
379                 analog->meaning->mq = SR_MQ_FREQUENCY;
380                 analog->meaning->unit = SR_UNIT_HERTZ;
381                 break;
382         case MODE_LOGIC:
383                 /*
384                  * No matter whether or not we have an actual voltage reading,
385                  * we are measuring voltage, so we set our MQ as VOLTAGE.
386                  */
387                 analog->meaning->mq = SR_MQ_VOLTAGE;
388                 if (!isnan(rawval)) {
389                         /* We have an actual voltage. */
390                         analog->meaning->unit = SR_UNIT_VOLT;
391                 } else {
392                         /* We have either HI or LOW. */
393                         analog->meaning->unit = SR_UNIT_BOOLEAN;
394                         rawval = is_logic_high(rs_packet);
395                 }
396                 break;
397         case MODE_HFE:
398                 analog->meaning->mq = SR_MQ_GAIN;
399                 analog->meaning->unit = SR_UNIT_UNITLESS;
400                 break;
401         case MODE_DUTY:         /* Fall through */
402         case MODE_VOLT_DUTY:    /* Fall through */
403         case MODE_AMP_DUTY:
404                 analog->meaning->mq = SR_MQ_DUTY_CYCLE;
405                 analog->meaning->unit = SR_UNIT_PERCENTAGE;
406                 break;
407         case MODE_WIDTH:        /* Fall through */
408         case MODE_VOLT_WIDTH:   /* Fall through */
409         case MODE_AMP_WIDTH:
410                 analog->meaning->mq = SR_MQ_PULSE_WIDTH;
411                 analog->meaning->unit = SR_UNIT_SECOND;
412                 break;
413         case MODE_TEMP:
414                 analog->meaning->mq = SR_MQ_TEMPERATURE;
415                 /* We need to reparse. */
416                 rawval = lcd_to_double(rs_packet, READ_TEMP, &exponent);
417                 analog->meaning->unit = is_celsius(rs_packet) ?
418                                 SR_UNIT_CELSIUS : SR_UNIT_FAHRENHEIT;
419                 break;
420         case MODE_DBM:
421                 analog->meaning->mq = SR_MQ_POWER;
422                 analog->meaning->unit = SR_UNIT_DECIBEL_MW;
423                 analog->meaning->mqflags |= SR_MQFLAG_AC;
424                 break;
425         default:
426                 sr_dbg("Unknown mode: %d.", rs_packet->mode);
427                 break;
428         }
429
430         if (rs_packet->info & INFO_HOLD)
431                 analog->meaning->mqflags |= SR_MQFLAG_HOLD;
432         if (rs_packet->digit4 & DIG4_MAX)
433                 analog->meaning->mqflags |= SR_MQFLAG_MAX;
434         if (rs_packet->indicatrix2 & IND2_MIN)
435                 analog->meaning->mqflags |= SR_MQFLAG_MIN;
436         if (rs_packet->info & INFO_AUTO)
437                 analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE;
438
439         *floatval = rawval;
440
441         analog->encoding->digits = -exponent;
442         analog->spec->spec_digits = -exponent;
443
444         return SR_OK;
445 }