]>
Commit | Line | Data |
---|---|---|
05f134ab | 1 | /* |
50985c20 | 2 | * This file is part of the libsigrok project. |
05f134ab AG |
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 | ||
6ec6c43b | 31 | #include <config.h> |
05f134ab AG |
32 | #include <string.h> |
33 | #include <ctype.h> | |
34 | #include <math.h> | |
35 | #include <glib.h> | |
c1aae900 | 36 | #include <libsigrok/libsigrok.h> |
05f134ab AG |
37 | #include "libsigrok-internal.h" |
38 | ||
b95dd761 | 39 | #define LOG_PREFIX "rs9lcd" |
05f134ab AG |
40 | |
41 | /* Byte 1 of the packet, and the modes it represents */ | |
0853d5e6 AG |
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) | |
05f134ab | 50 | /* Byte 2 of the packet, and the modes it represents */ |
0853d5e6 AG |
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) | |
05f134ab | 59 | /* Byte 7 of the packet, and the modes it represents */ |
0853d5e6 AG |
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) | |
05f134ab | 68 | /* Instead of a decimal point, digit 4 carries the MAX flag */ |
0853d5e6 | 69 | #define DIG4_MAX (1 << 3) |
05f134ab | 70 | /* Mask to remove the decimal point from a digit */ |
0853d5e6 | 71 | #define DP_MASK (1 << 3) |
05f134ab AG |
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, | |
0853d5e6 AG |
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 */ | |
05f134ab | 109 | MODE_DUTY = 13, |
0853d5e6 AG |
110 | MODE_VOLT_DUTY = 14, /* Dial set to V, duty cycle selected */ |
111 | MODE_AMP_DUTY = 15, /* Dial set to A, duty cycle selected */ | |
05f134ab | 112 | MODE_WIDTH = 16, |
0853d5e6 AG |
113 | MODE_VOLT_WIDTH = 17, /* Dial set to V, pulse width selected */ |
114 | MODE_AMP_WIDTH = 18, /* Dial set to A, pulse width selected */ | |
05f134ab AG |
115 | MODE_DIODE = 19, |
116 | MODE_CONT = 20, | |
117 | MODE_HFE = 21, | |
118 | MODE_LOGIC = 22, | |
119 | MODE_DBM = 23, | |
0853d5e6 | 120 | /* MODE_EF = 24, */ /* Not encountered on any DMM */ |
05f134ab AG |
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 | ||
21829e67 | 150 | for (i = 0; i < RS9LCD_PACKET_SIZE - 1; i++) |
05f134ab AG |
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) { | |
ec5186f9 | 171 | sr_dbg("More than one multiplier detected in packet."); |
05f134ab AG |
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) { | |
ec5186f9 | 187 | sr_dbg("More than one measurement type detected in packet."); |
05f134ab AG |
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: | |
ec5186f9 | 252 | sr_dbg("Invalid digit byte: 0x%02x.", raw_digit); |
05f134ab AG |
253 | return 0xff; |
254 | } | |
255 | } | |
256 | ||
96a06b42 AJ |
257 | static double lcd_to_double(const struct rs9lcd_packet *rs_packet, int type, |
258 | int *exponent) | |
05f134ab | 259 | { |
96a06b42 | 260 | double rawval = 0; |
05f134ab AG |
261 | uint8_t digit, raw_digit; |
262 | gboolean dp_reached = FALSE; | |
263 | int i, end; | |
264 | ||
96a06b42 AJ |
265 | *exponent = 0; |
266 | ||
05f134ab AG |
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) | |
96a06b42 | 285 | *exponent -= 1; |
05f134ab AG |
286 | rawval = rawval * 10 + digit; |
287 | } | |
05f134ab AG |
288 | if (rs_packet->info & INFO_NEG) |
289 | rawval *= -1; | |
290 | ||
291 | /* See if we need to multiply our raw value by anything. */ | |
6703bb29 | 292 | if (rs_packet->indicatrix2 & IND2_NANO) |
96a06b42 | 293 | *exponent -= 9; |
05f134ab | 294 | else if (rs_packet->indicatrix2 & IND2_MICRO) |
96a06b42 | 295 | *exponent -= 6; |
05f134ab | 296 | else if (rs_packet->indicatrix1 & IND1_MILI) |
96a06b42 | 297 | *exponent -= 3; |
05f134ab | 298 | else if (rs_packet->indicatrix1 & IND1_KILO) |
96a06b42 | 299 | *exponent += 3; |
05f134ab | 300 | else if (rs_packet->indicatrix1 & IND1_MEGA) |
96a06b42 | 301 | *exponent += 6; |
05f134ab | 302 | |
96a06b42 | 303 | return rawval * powf(10, *exponent); |
05f134ab AG |
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, | |
b02bb45f | 323 | struct sr_datafeed_analog *analog, void *info) |
05f134ab AG |
324 | { |
325 | const struct rs9lcd_packet *rs_packet = (void *)buf; | |
96a06b42 | 326 | int exponent; |
05f134ab AG |
327 | double rawval; |
328 | ||
329 | (void)info; | |
330 | ||
96a06b42 | 331 | rawval = lcd_to_double(rs_packet, READ_ALL, &exponent); |
05f134ab AG |
332 | |
333 | switch (rs_packet->mode) { | |
334 | case MODE_DC_V: | |
b02bb45f UH |
335 | analog->meaning->mq = SR_MQ_VOLTAGE; |
336 | analog->meaning->unit = SR_UNIT_VOLT; | |
337 | analog->meaning->mqflags |= SR_MQFLAG_DC; | |
05f134ab AG |
338 | break; |
339 | case MODE_AC_V: | |
b02bb45f UH |
340 | analog->meaning->mq = SR_MQ_VOLTAGE; |
341 | analog->meaning->unit = SR_UNIT_VOLT; | |
342 | analog->meaning->mqflags |= SR_MQFLAG_AC; | |
05f134ab | 343 | break; |
0853d5e6 AG |
344 | case MODE_DC_UA: /* Fall through */ |
345 | case MODE_DC_MA: /* Fall through */ | |
05f134ab | 346 | case MODE_DC_A: |
b02bb45f UH |
347 | analog->meaning->mq = SR_MQ_CURRENT; |
348 | analog->meaning->unit = SR_UNIT_AMPERE; | |
349 | analog->meaning->mqflags |= SR_MQFLAG_DC; | |
05f134ab | 350 | break; |
0853d5e6 AG |
351 | case MODE_AC_UA: /* Fall through */ |
352 | case MODE_AC_MA: /* Fall through */ | |
05f134ab | 353 | case MODE_AC_A: |
b02bb45f UH |
354 | analog->meaning->mq = SR_MQ_CURRENT; |
355 | analog->meaning->unit = SR_UNIT_AMPERE; | |
356 | analog->meaning->mqflags |= SR_MQFLAG_AC; | |
05f134ab AG |
357 | break; |
358 | case MODE_OHM: | |
b02bb45f UH |
359 | analog->meaning->mq = SR_MQ_RESISTANCE; |
360 | analog->meaning->unit = SR_UNIT_OHM; | |
05f134ab AG |
361 | break; |
362 | case MODE_FARAD: | |
b02bb45f UH |
363 | analog->meaning->mq = SR_MQ_CAPACITANCE; |
364 | analog->meaning->unit = SR_UNIT_FARAD; | |
05f134ab AG |
365 | break; |
366 | case MODE_CONT: | |
b02bb45f UH |
367 | analog->meaning->mq = SR_MQ_CONTINUITY; |
368 | analog->meaning->unit = SR_UNIT_BOOLEAN; | |
47eda193 | 369 | rawval = is_shortcirc(rs_packet); |
05f134ab AG |
370 | break; |
371 | case MODE_DIODE: | |
b02bb45f UH |
372 | analog->meaning->mq = SR_MQ_VOLTAGE; |
373 | analog->meaning->unit = SR_UNIT_VOLT; | |
374 | analog->meaning->mqflags |= SR_MQFLAG_DIODE | SR_MQFLAG_DC; | |
05f134ab | 375 | break; |
0853d5e6 AG |
376 | case MODE_HZ: /* Fall through */ |
377 | case MODE_VOLT_HZ: /* Fall through */ | |
05f134ab | 378 | case MODE_AMP_HZ: |
b02bb45f UH |
379 | analog->meaning->mq = SR_MQ_FREQUENCY; |
380 | analog->meaning->unit = SR_UNIT_HERTZ; | |
05f134ab AG |
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 | */ | |
b02bb45f | 387 | analog->meaning->mq = SR_MQ_VOLTAGE; |
05f134ab AG |
388 | if (!isnan(rawval)) { |
389 | /* We have an actual voltage. */ | |
b02bb45f | 390 | analog->meaning->unit = SR_UNIT_VOLT; |
05f134ab AG |
391 | } else { |
392 | /* We have either HI or LOW. */ | |
b02bb45f | 393 | analog->meaning->unit = SR_UNIT_BOOLEAN; |
47eda193 | 394 | rawval = is_logic_high(rs_packet); |
05f134ab AG |
395 | } |
396 | break; | |
397 | case MODE_HFE: | |
b02bb45f UH |
398 | analog->meaning->mq = SR_MQ_GAIN; |
399 | analog->meaning->unit = SR_UNIT_UNITLESS; | |
05f134ab | 400 | break; |
0853d5e6 AG |
401 | case MODE_DUTY: /* Fall through */ |
402 | case MODE_VOLT_DUTY: /* Fall through */ | |
05f134ab | 403 | case MODE_AMP_DUTY: |
b02bb45f UH |
404 | analog->meaning->mq = SR_MQ_DUTY_CYCLE; |
405 | analog->meaning->unit = SR_UNIT_PERCENTAGE; | |
05f134ab | 406 | break; |
0853d5e6 AG |
407 | case MODE_WIDTH: /* Fall through */ |
408 | case MODE_VOLT_WIDTH: /* Fall through */ | |
05f134ab | 409 | case MODE_AMP_WIDTH: |
b02bb45f UH |
410 | analog->meaning->mq = SR_MQ_PULSE_WIDTH; |
411 | analog->meaning->unit = SR_UNIT_SECOND; | |
cbc8cbd8 | 412 | break; |
05f134ab | 413 | case MODE_TEMP: |
b02bb45f | 414 | analog->meaning->mq = SR_MQ_TEMPERATURE; |
05f134ab | 415 | /* We need to reparse. */ |
96a06b42 | 416 | rawval = lcd_to_double(rs_packet, READ_TEMP, &exponent); |
b02bb45f | 417 | analog->meaning->unit = is_celsius(rs_packet) ? |
05f134ab AG |
418 | SR_UNIT_CELSIUS : SR_UNIT_FAHRENHEIT; |
419 | break; | |
420 | case MODE_DBM: | |
b02bb45f UH |
421 | analog->meaning->mq = SR_MQ_POWER; |
422 | analog->meaning->unit = SR_UNIT_DECIBEL_MW; | |
423 | analog->meaning->mqflags |= SR_MQFLAG_AC; | |
05f134ab AG |
424 | break; |
425 | default: | |
ec5186f9 | 426 | sr_dbg("Unknown mode: %d.", rs_packet->mode); |
05f134ab AG |
427 | break; |
428 | } | |
429 | ||
430 | if (rs_packet->info & INFO_HOLD) | |
b02bb45f | 431 | analog->meaning->mqflags |= SR_MQFLAG_HOLD; |
05f134ab | 432 | if (rs_packet->digit4 & DIG4_MAX) |
b02bb45f | 433 | analog->meaning->mqflags |= SR_MQFLAG_MAX; |
05f134ab | 434 | if (rs_packet->indicatrix2 & IND2_MIN) |
b02bb45f | 435 | analog->meaning->mqflags |= SR_MQFLAG_MIN; |
05f134ab | 436 | if (rs_packet->info & INFO_AUTO) |
b02bb45f | 437 | analog->meaning->mqflags |= SR_MQFLAG_AUTORANGE; |
05f134ab AG |
438 | |
439 | *floatval = rawval; | |
96a06b42 | 440 | |
d9251a2c | 441 | analog->encoding->digits = -exponent; |
96a06b42 AJ |
442 | analog->spec->spec_digits = -exponent; |
443 | ||
05f134ab AG |
444 | return SR_OK; |
445 | } |