]>
Commit | Line | Data |
---|---|---|
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.h" | |
36 | #include "libsigrok-internal.h" | |
37 | ||
38 | /* Message logging helpers with subsystem-specific prefix string. */ | |
39 | #define LOG_PREFIX "rs9lcd: " | |
40 | #define sr_log(l, s, args...) sr_log(l, LOG_PREFIX s, ## args) | |
41 | #define sr_spew(s, args...) sr_spew(LOG_PREFIX s, ## args) | |
42 | #define sr_dbg(s, args...) sr_dbg(LOG_PREFIX s, ## args) | |
43 | #define sr_info(s, args...) sr_info(LOG_PREFIX s, ## args) | |
44 | #define sr_warn(s, args...) sr_warn(LOG_PREFIX s, ## args) | |
45 | #define sr_err(s, args...) sr_err(LOG_PREFIX 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 | } |