]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * This file is part of the sigrok project. | |
3 | * | |
4 | * Copyright (C) 2012 Bert Vermeulen <bert@biot.com> | |
5 | * Copyright (C) 2012 Alexandru Gagniuc <mr.nuke.me@gmail.com> | |
6 | * | |
7 | * This program is free software: you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation, either version 3 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | */ | |
20 | ||
21 | #include <glib.h> | |
22 | #include "libsigrok.h" | |
23 | #include "libsigrok-internal.h" | |
24 | #include "radioshack-dmm.h" | |
25 | #include <stdlib.h> | |
26 | #include <math.h> | |
27 | #include <string.h> | |
28 | #include <errno.h> | |
29 | ||
30 | ||
31 | static gboolean rs_22_812_is_checksum_valid(const rs_22_812_packet *data) | |
32 | { | |
33 | uint8_t *raw = (void *) data; | |
34 | uint8_t sum = 0; | |
35 | size_t i; | |
36 | for(i = 0; i < RS_22_812_PACKET_SIZE - 1; i++) | |
37 | sum += raw[i]; | |
38 | /* This is just a funky constant added to the checksum */ | |
39 | sum += 57; | |
40 | sum -= data->checksum; | |
41 | return(sum == 0); | |
42 | } | |
43 | ||
44 | static gboolean rs_22_812_is_mode_valid(rs_22_812_mode mode) | |
45 | { | |
46 | return(mode < RS_22_812_MODE_INVALID); | |
47 | } | |
48 | ||
49 | static gboolean rs_22_812_is_selection_good(const rs_22_812_packet *data) | |
50 | { | |
51 | int n_postfix = 0; | |
52 | int n_type = 0; | |
53 | /* Does the packet have more than one multiplier ? */ | |
54 | if(data->indicatrix1 & RS_22_812_IND1_KILO) | |
55 | n_postfix++; | |
56 | if(data->indicatrix1 & RS_22_812_IND1_MEGA) | |
57 | n_postfix++; | |
58 | if(data->indicatrix1 & RS_22_812_IND1_MILI) | |
59 | n_postfix++; | |
60 | if(data->indicatrix2 & RS_22_812_IND2_MICRO) | |
61 | n_postfix++; | |
62 | if(data->indicatrix2 & RS_22_812_IND2_NANO) | |
63 | n_postfix++; | |
64 | if(n_postfix > 1) | |
65 | return FALSE; | |
66 | ||
67 | /* Does the packet "measure" more than one type of value ?*/ | |
68 | if(data->indicatrix1 & RS_22_812_IND1_HZ) | |
69 | n_type++; | |
70 | if(data->indicatrix1 & RS_22_812_IND1_OHM) | |
71 | n_type++; | |
72 | if(data->indicatrix1 & RS_22_812_IND1_FARAD) | |
73 | n_type++; | |
74 | if(data->indicatrix1 & RS_22_812_IND1_AMP) | |
75 | n_type++; | |
76 | if(data->indicatrix1 & RS_22_812_IND1_VOLT) | |
77 | n_type++; | |
78 | if(data->indicatrix2 & RS_22_812_IND2_DBM) | |
79 | n_type++; | |
80 | if(data->indicatrix2 & RS_22_812_IND2_SEC) | |
81 | n_type++; | |
82 | if(data->indicatrix2 & RS_22_812_IND2_DUTY) | |
83 | n_type++; | |
84 | if(data->indicatrix2 & RS_22_812_IND2_HFE) | |
85 | n_type++; | |
86 | if(n_type > 1) | |
87 | return FALSE; | |
88 | ||
89 | /* OK, no duplicates */ | |
90 | return TRUE; | |
91 | } | |
92 | ||
93 | /* Since the RS 22-812 does not identify itslef in any way shape, or form, | |
94 | * we really don't know for sure who is sending the data. We must use every | |
95 | * possible check to filter out bad packets, especially since detection of the | |
96 | * 22-812 depends on how well we can filter the packets */ | |
97 | SR_PRIV gboolean rs_22_812_is_packet_valid(const rs_22_812_packet *packet) | |
98 | { | |
99 | /* Unfortunately, the packet doesn't have a signature, so we must | |
100 | * compute its checksum first */ | |
101 | if(!rs_22_812_is_checksum_valid(packet)) | |
102 | return FALSE; | |
103 | ||
104 | if(!rs_22_812_is_mode_valid(packet->mode)) | |
105 | return FALSE; | |
106 | ||
107 | if(!rs_22_812_is_selection_good(packet)) { | |
108 | return FALSE; | |
109 | } | |
110 | /* Made it here, huh? Then this looks to be a valid packet */ | |
111 | return TRUE; | |
112 | } | |
113 | ||
114 | static uint8_t rs_22_812_to_digit(uint8_t raw_digit) | |
115 | { | |
116 | /* Take out the decimal point, so we can use a simple switch() */ | |
117 | raw_digit &= ~RS_22_812_DP_MASK; | |
118 | switch(raw_digit) | |
119 | { | |
120 | case 0x00: | |
121 | case RS_22_812_LCD_0: | |
122 | return 0; | |
123 | case RS_22_812_LCD_1: | |
124 | return 1; | |
125 | case RS_22_812_LCD_2: | |
126 | return 2; | |
127 | case RS_22_812_LCD_3: | |
128 | return 3; | |
129 | case RS_22_812_LCD_4: | |
130 | return 4; | |
131 | case RS_22_812_LCD_5: | |
132 | return 5; | |
133 | case RS_22_812_LCD_6: | |
134 | return 6; | |
135 | case RS_22_812_LCD_7: | |
136 | return 7; | |
137 | case RS_22_812_LCD_8: | |
138 | return 8; | |
139 | case RS_22_812_LCD_9: | |
140 | return 9; | |
141 | default: | |
142 | return 0xff; | |
143 | } | |
144 | } | |
145 | ||
146 | typedef enum { | |
147 | READ_ALL, | |
148 | READ_TEMP, | |
149 | } value_type; | |
150 | ||
151 | static double lcdraw_to_double(rs_22_812_packet *rs_packet, value_type type) | |
152 | { | |
153 | /* ********************************************************************* | |
154 | * Get a raw floating point value from the data | |
155 | **********************************************************************/ | |
156 | double rawval; | |
157 | double multiplier = 1; | |
158 | uint8_t digit; | |
159 | gboolean dp_reached = FALSE; | |
160 | int i, end; | |
161 | switch(type) { | |
162 | case READ_TEMP: | |
163 | /* Do not parse the last digit */ | |
164 | end = 1; | |
165 | break; | |
166 | case READ_ALL: | |
167 | default: | |
168 | /* Parse all digits */ | |
169 | end = 0; | |
170 | } | |
171 | /* We have 4 digits, and we start from the most significant */ | |
172 | for(i = 3; i >= end; i--) | |
173 | { | |
174 | uint8_t raw_digit = *(&(rs_packet->digit4) + i); | |
175 | digit = rs_22_812_to_digit(raw_digit); | |
176 | if(digit == 0xff) { | |
177 | rawval = NAN; | |
178 | break; | |
179 | } | |
180 | /* Digit 1 does not have a decimal point. Instead, the decimal | |
181 | * point is used to indicate MAX, so we must avoid testing it */ | |
182 | if( (i < 3) && (raw_digit & RS_22_812_DP_MASK) ) | |
183 | dp_reached = TRUE; | |
184 | if(dp_reached) multiplier /= 10; | |
185 | rawval = rawval * 10 + digit; | |
186 | } | |
187 | rawval *= multiplier; | |
188 | if(rs_packet->info & RS_22_812_INFO_NEG) | |
189 | rawval *= -1; | |
190 | ||
191 | /* See if we need to multiply our raw value by anything */ | |
192 | if(rs_packet->indicatrix1 & RS_22_812_IND2_NANO) { | |
193 | rawval *= 1E-9; | |
194 | } else if(rs_packet->indicatrix2 & RS_22_812_IND2_MICRO) { | |
195 | rawval *= 1E-6; | |
196 | } else if(rs_packet->indicatrix1 & RS_22_812_IND1_MILI) { | |
197 | rawval *= 1E-3; | |
198 | } else if(rs_packet->indicatrix1 & RS_22_812_IND1_KILO) { | |
199 | rawval *= 1E3; | |
200 | } else if(rs_packet->indicatrix1 & RS_22_812_IND1_MEGA) { | |
201 | rawval *= 1E6; | |
202 | } | |
203 | ||
204 | return rawval; | |
205 | } | |
206 | ||
207 | static gboolean rs_22_812_is_celsius(rs_22_812_packet *rs_packet) | |
208 | { | |
209 | return((rs_packet->digit4 & ~RS_22_812_DP_MASK) == RS_22_812_LCD_C); | |
210 | } | |
211 | ||
212 | static gboolean rs_22_812_is_shortcirc(rs_22_812_packet *rs_packet) | |
213 | { | |
214 | return((rs_packet->digit2 & ~RS_22_812_DP_MASK) == RS_22_812_LCD_h); | |
215 | } | |
216 | ||
217 | static gboolean rs_22_812_is_logic_high(rs_22_812_packet *rs_packet) | |
218 | { | |
219 | sr_spew("digit 2: %x", rs_packet->digit2 & ~RS_22_812_DP_MASK); | |
220 | return((rs_packet->digit2 & ~RS_22_812_DP_MASK) == RS_22_812_LCD_H); | |
221 | } | |
222 | ||
223 | static void rs_22_812_handle_packet(rs_22_812_packet *rs_packet, | |
224 | rs_dev_ctx *devc) | |
225 | { | |
226 | double rawval = lcdraw_to_double(rs_packet, READ_ALL); | |
227 | /* ********************************************************************* | |
228 | * Now see what the value means, and pass that on | |
229 | **********************************************************************/ | |
230 | struct sr_datafeed_packet packet; | |
231 | struct sr_datafeed_analog *analog; | |
232 | ||
233 | analog = g_try_malloc0(sizeof(struct sr_datafeed_analog)); | |
234 | analog->num_samples = 1; | |
235 | analog->data = g_try_malloc(sizeof(float)); | |
236 | *analog->data = (float)rawval; | |
237 | analog->mq = -1; | |
238 | ||
239 | switch(rs_packet->mode) { | |
240 | case RS_22_812_MODE_DC_V: | |
241 | analog->mq = SR_MQ_VOLTAGE; | |
242 | analog->unit = SR_UNIT_VOLT; | |
243 | analog->mqflags |= SR_MQFLAG_DC; | |
244 | break; | |
245 | case RS_22_812_MODE_AC_V: | |
246 | analog->mq = SR_MQ_VOLTAGE; | |
247 | analog->unit = SR_UNIT_VOLT; | |
248 | analog->mqflags |= SR_MQFLAG_AC; | |
249 | break; | |
250 | case RS_22_812_MODE_DC_UA: | |
251 | case RS_22_812_MODE_DC_MA: | |
252 | case RS_22_812_MODE_DC_A: | |
253 | analog->mq = SR_MQ_CURRENT; | |
254 | analog->unit = SR_UNIT_AMPERE; | |
255 | analog->mqflags |= SR_MQFLAG_DC; | |
256 | break; | |
257 | case RS_22_812_MODE_AC_UA: | |
258 | case RS_22_812_MODE_AC_MA: | |
259 | case RS_22_812_MODE_AC_A: | |
260 | analog->mq = SR_MQ_CURRENT; | |
261 | analog->unit = SR_UNIT_AMPERE; | |
262 | analog->mqflags |= SR_MQFLAG_AC; | |
263 | break; | |
264 | case RS_22_812_MODE_OHM: | |
265 | analog->mq = SR_MQ_RESISTANCE; | |
266 | analog->unit = SR_UNIT_OHM; | |
267 | break; | |
268 | case RS_22_812_MODE_FARAD: | |
269 | analog->mq = SR_MQ_CAPACITANCE; | |
270 | analog->unit = SR_UNIT_FARAD; | |
271 | break; | |
272 | case RS_22_812_MODE_CONT: | |
273 | analog->mq = SR_MQ_CONTINUITY; | |
274 | analog->unit = SR_UNIT_BOOLEAN; | |
275 | *analog->data = rs_22_812_is_shortcirc(rs_packet); | |
276 | break; | |
277 | case RS_22_812_MODE_DIODE: | |
278 | analog->mq = SR_MQ_VOLTAGE; | |
279 | analog->unit = SR_UNIT_VOLT; | |
280 | analog->mqflags |= SR_MQFLAG_DIODE | SR_MQFLAG_DC; | |
281 | break; | |
282 | case RS_22_812_MODE_HZ: | |
283 | case RS_22_812_MODE_VOLT_HZ: | |
284 | case RS_22_812_MODE_AMP_HZ: | |
285 | analog->mq = SR_MQ_FREQUENCY; | |
286 | analog->unit = SR_UNIT_HERTZ; | |
287 | break; | |
288 | case RS_22_812_MODE_LOGIC: | |
289 | /* No matter whether or not we have an actual voltage reading, | |
290 | * we are measuring voltage, so we set our MQ as VOLTAGE */ | |
291 | analog->mq = SR_MQ_VOLTAGE; | |
292 | if(!isnan(rawval)) { | |
293 | /* We have an actual voltage */ | |
294 | analog->unit = SR_UNIT_VOLT; | |
295 | } else { | |
296 | /* We have either HI or LOW */ | |
297 | analog->unit = SR_UNIT_BOOLEAN; | |
298 | *analog->data = rs_22_812_is_logic_high(rs_packet); | |
299 | } | |
300 | break; | |
301 | case RS_22_812_MODE_HFE: | |
302 | analog->mq = SR_MQ_GAIN; | |
303 | analog->unit = SR_UNIT_UNITLESS; | |
304 | break; | |
305 | case RS_22_812_MODE_DUTY: | |
306 | case RS_22_812_MODE_VOLT_DUTY: | |
307 | case RS_22_812_MODE_AMP_DUTY: | |
308 | analog->mq = SR_MQ_DUTY_CYCLE; | |
309 | analog->unit = SR_UNIT_PERCENTAGE; | |
310 | break; | |
311 | case RS_22_812_MODE_WIDTH: | |
312 | case RS_22_812_MODE_VOLT_WIDTH: | |
313 | case RS_22_812_MODE_AMP_WIDTH: | |
314 | analog->mq = SR_MQ_PULSE_WIDTH; | |
315 | analog->unit = SR_UNIT_SECOND; | |
316 | case RS_22_812_MODE_TEMP: | |
317 | analog->mq = SR_MQ_TEMPERATURE; | |
318 | /* We need to reparse */ | |
319 | *analog->data = lcdraw_to_double(rs_packet, READ_TEMP); | |
320 | analog->unit = rs_22_812_is_celsius(rs_packet)? | |
321 | SR_UNIT_CELSIUS:SR_UNIT_FAHRENHEIT; | |
322 | break; | |
323 | case RS_22_812_MODE_DBM: | |
324 | analog->mq = SR_MQ_POWER; | |
325 | analog->unit = SR_UNIT_DECIBEL_MW; | |
326 | analog->mqflags |= SR_MQFLAG_AC; | |
327 | break; | |
328 | default: | |
329 | sr_warn("radioshack-dmm: unkown mode: %d", rs_packet->mode); | |
330 | break; | |
331 | } | |
332 | ||
333 | if(rs_packet->info & RS_22_812_INFO_HOLD) { | |
334 | analog->mqflags |= SR_MQFLAG_HOLD; | |
335 | } | |
336 | if(rs_packet->digit4 & RS_22_812_DIG4_MAX) { | |
337 | analog->mqflags |= SR_MQFLAG_MAX; | |
338 | } | |
339 | if(rs_packet->indicatrix2 & RS_22_812_IND2_MIN) { | |
340 | analog->mqflags |= SR_MQFLAG_MIN; | |
341 | } | |
342 | if(rs_packet->info & RS_22_812_INFO_AUTO) { | |
343 | analog->mqflags |= SR_MQFLAG_AUTORANGE; | |
344 | } | |
345 | ||
346 | if (analog->mq != -1) { | |
347 | /* Got a measurement. */ | |
348 | sr_spew("radioshack-dmm: val %f", rawval); | |
349 | packet.type = SR_DF_ANALOG; | |
350 | packet.payload = analog; | |
351 | sr_session_send(devc->cb_data, &packet); | |
352 | devc->num_samples++; | |
353 | } | |
354 | g_free(analog->data); | |
355 | g_free(analog); | |
356 | } | |
357 | ||
358 | static void handle_new_data(rs_dev_ctx *devc, int fd) | |
359 | { | |
360 | int len; | |
361 | size_t i; | |
362 | size_t offset = 0; | |
363 | /* Try to get as much data as the buffer can hold */ | |
364 | len = RS_DMM_BUFSIZE - devc->buflen; | |
365 | len = serial_read(fd, devc->buf + devc->buflen, len); | |
366 | if (len < 1) { | |
367 | sr_err("radioshack-dmm: serial port read error!"); | |
368 | return; | |
369 | } | |
370 | devc->buflen += len; | |
371 | ||
372 | /* Now look for packets in that data */ | |
373 | while((devc->buflen - offset) >= RS_22_812_PACKET_SIZE) | |
374 | { | |
375 | rs_22_812_packet * packet = (void *)(devc->buf + offset); | |
376 | if( rs_22_812_is_packet_valid(packet) ) | |
377 | { | |
378 | rs_22_812_handle_packet(packet, devc); | |
379 | offset += RS_22_812_PACKET_SIZE; | |
380 | } else { | |
381 | offset++; | |
382 | } | |
383 | } | |
384 | ||
385 | /* If we have any data left, move it to the beginning of our buffer */ | |
386 | for(i = 0; i < devc->buflen - offset; i++) | |
387 | devc->buf[i] = devc->buf[offset + i]; | |
388 | devc->buflen -= offset; | |
389 | } | |
390 | ||
391 | SR_PRIV int radioshack_receive_data(int fd, int revents, void *cb_data) | |
392 | { | |
393 | const struct sr_dev_inst *sdi; | |
394 | struct dev_context *devc; | |
395 | ||
396 | if (!(sdi = cb_data)) | |
397 | return TRUE; | |
398 | ||
399 | if (!(devc = sdi->priv)) | |
400 | return TRUE; | |
401 | ||
402 | if (revents == G_IO_IN) | |
403 | { | |
404 | /* Serial data arrived. */ | |
405 | handle_new_data(devc, fd); | |
406 | } | |
407 | ||
408 | if (devc->num_samples >= devc->limit_samples) { | |
409 | sdi->driver->dev_acquisition_stop(sdi, cb_data); | |
410 | return TRUE; | |
411 | } | |
412 | ||
413 | return TRUE; | |
414 | } | |
415 |