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