]> sigrok.org Git - libsigrok.git/blame - src/dmm/rs9lcd.c
output/csv: use intermediate time_t var, silence compiler warning
[libsigrok.git] / src / dmm / rs9lcd.c
CommitLineData
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
95enum {
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
125enum {
126 READ_ALL,
127 READ_TEMP,
128};
129
130struct 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
142static 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
159static 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 */
200SR_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
224static 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
257static 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
306static gboolean is_celsius(const struct rs9lcd_packet *rs_packet)
307{
308 return ((rs_packet->digit4 & ~DP_MASK) == LCD_C);
309}
310
311static gboolean is_shortcirc(const struct rs9lcd_packet *rs_packet)
312{
313 return ((rs_packet->digit2 & ~DP_MASK) == LCD_h);
314}
315
316static 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
322SR_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}