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