-
Notifications
You must be signed in to change notification settings - Fork 0
/
rs22812.py
executable file
·307 lines (280 loc) · 10.6 KB
/
rs22812.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#!/usr/bin/env python2
'''
A python interface for the Radio Shack 22-812 digital multimeter. You will
also need to download and install the PySerial module (see
http://pyserial.sourceforge.net/). This Radio Shack multimeter has a
serial port that lets you get the readings the instrument makes.
Modern computers usually do not come with a serial port anymore. You
can buy a USB to serial adapter for $15 or so that will give you a serial
port. Sabrent is one brand name, but there are many others. The device
plugs into a USB port and terminates in a 9 pin RS-232 connector (mine is
a male connector). For Windows, you install a driver by running an
executable and you should then have a working serial port at COM3. No
computer reboot is necessary.
This routine was written based on the information in the article
http://www.kronosrobotics.com/Projects/CYW_RSMeter.pdf (update 22 Jun
2014: this website is defunct). Note the numbering of bits in Table
1 in the article is opposite of the typical convention: bit 7 is
actually what most folks would call bit 0. Here is the table with the
bit numbering as I would prefer:
Bit
Byte 7 6 5 4 3 2 1 0
0 ---------------------- Mode -----------------------------
1 Hz Ohms K M F A V m
2 u n dBm s % hFE REL MIN
3 4D 4C 4G 4B DP3 4E 4F 4A
4 3D 3C 3G 3B DP2 3E 3F 3A
5 2D 2C 2G 2B DP1 2E 2F 2A
6 1D 1C 1G 1B MAX 1E 1F 1A
7 Beep Diode Bat Hold - ~ RS232 Auto
8 -------------------- Checksum ---------------------------
The segment lettering is
|--A--|
| |
F B
| |
|--G--|
| |
E C
| |
|--D--|
The mode is given by the following table:
0 DC V
1 AC V
2 DC uA
3 DC mA
4 DC A
5 AC uA
6 AC mA
7 AC A
8 OHM
9 CAP
10 HZ
11 NET HZ
12 AMP HZ
13 DUTY
14 NET DUTY
15 AMP DUTY
16 WIDTH
17 NET WIDTH
18 AMP WIDTH
19 DIODE
20 CONT
21 HFE
22 LOGIC
23 DBM
24 EF
25 TEMP
Note: I don't know what "NET HZ" etc. mean, as I didn't see these come
up during my testing. Perhaps they are available with later instruments; I
bought mine around 2001.
The interface works at 4800 baud, 8 bits, no parity and 1 stop bit. Once
you have made the connection, your program needs to raise DTR (data terminal
ready) and the meter will start sending data. Note you have to press the
SELECT and RANGE buttons on the meter simultaneously to enable the RS-232
interface.
The meter uses a 9 V battery and the manual states that the battery will
last for about 100 hours. It is probably less than this when the serial
interface is being used.
But for $70, this lets an experimenter log measurement data unattended.
The meter has a 9 pin female D connector for the serial port connection.
----------------------------------------------------------------------
Copyright (C) 2009, 2014 Don Peterson
Contact: gmail.com@someonesdad1
The Wide Open License (WOL)
Permission to use, copy, modify, distribute and sell this software and
its documentation for any purpose is hereby granted without fee,
provided that the above copyright notice and this license appear in
all copies. THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR
IMPLIED WARRANTY OF ANY KIND. See
http://www.dspguru.com/wide-open-license for more information.
'''
import serial
from time import sleep
ignore_RS232_modifier = True
class RS22812(object):
'''Provides an interface object to the Radio Shack 22-812 digital
multimeter. You must provide the constructor with the COM port number
or device.
'''
def __init__(self, com_port=3):
self.com_port = com_port
baudrate = 4800
self.sp = serial.Serial(com_port-1, baudrate, timeout=0)
def __del__(self):
if self.sp:
self.sp.close()
def get_packet(self):
'''This routine follows the logic of the algorithm given on page 2
of the article
http://www.kronosrobotics.com/Projects/CYW_RSMeter.pdf. The
procedure is:
Purge the COM port
Wait 100 ms
Set DTR true
Get 9 bytes
Set DTR false
Wait 100 ms
Purge the COM port
However, we continue to loop until we do get a good packet.
'''
sleep_time = 0.3 # seconds
good_packet = False
while not good_packet:
self.sp.flushInput()
sleep(sleep_time)
self.sp.dsrdtr = True
packet = self.sp.read(9)
if len(packet) != 9:
self.sp.dsrdtr = False
sleep(sleep_time)
continue
# I have no idea why the article adds this number in for the
# checksum, but it seems to work.
constant = 57
checksum = (sum([ord(c) for c in packet[:-1]]) + constant) & 255
if checksum != ord(packet[8]):
self.sp.dsrdtr = False
sleep(sleep_time)
continue
good_packet = True
self.sp.dsrdtr = False
sleep(sleep_time)
self.sp.flushInput()
return packet
def interpret_digit(self, byte):
'''This routine interprets the coded seven segment display digit.
'''
code = { 215 : "0", 80 : "1", 181 : "2", 241 : "3", 114 : "4",
227 : "5", 231 : "6", 81 : "7", 247 : "8", 243 : "9", 39 : "F",
55 : "P", 167 : "E", 135 : "C", 134 : "L", 118 : "H", 6 : "I",
102 : "h", 36 : "r", 166 : "t", 100 : "n", 32 : "-", 0 : " "}
if byte in code:
return code[byte]
else:
return "?"
def GetModifiers(self, bytes):
'''Various modes show additional annunciators, such as "MAX",
"MIN", etc. Return those set as a tuple of strings. We ignore
the RS-232 annunciator unless you really want it.
'''
s = []
if bytes[2] & (1 << 1): s += ["REL"]
if bytes[2] & (1 << 0): s += ["MIN"]
if bytes[6] & (1 << 3): s += ["MAX"]
if bytes[7] & (1 << 7): s += ["Beep"]
if bytes[7] & (1 << 6): s += ["Diode"]
if bytes[7] & (1 << 5): s += ["Bat"]
if bytes[7] & (1 << 4): s += ["Hold"]
if not ignore_RS232_modifier:
if bytes[7] & (1 << 1): s += ["RS232"]
if bytes[7] & (1 << 0): s += ["Auto"]
return tuple(s)
def GetUnits(self, bytes):
'''Return a string representing the units of the measurement.
'''
prefix = ""
if bytes[1] & (1 << 5): prefix = "k"
elif bytes[1] & (1 << 4): prefix = "M"
elif bytes[1] & (1 << 0): prefix = "m"
elif bytes[2] & (1 << 7): prefix = "u"
elif bytes[2] & (1 << 6): prefix = "n"
unit = ""
if bytes[1] & (1 << 7): unit = "Hz"
elif bytes[1] & (1 << 6): unit = "ohm"
elif bytes[1] & (1 << 3): unit = "F"
elif bytes[1] & (1 << 2): unit = "A"
elif bytes[1] & (1 << 1): unit = "V"
elif bytes[2] & (1 << 5): unit = "dBm"
elif bytes[2] & (1 << 4): unit = "s"
elif bytes[2] & (1 << 3): unit = "%"
elif bytes[2] & (1 << 2): unit = "hFE"
# Append "~" to V, A, or dBm if it is an AC measurement
if unit in ("V", "A", "dBm"):
if bytes[7] & (1 << 2): unit += "~"
if unit:
return prefix + unit
return ""
def InterpretReading(self, string):
'''We return a tuple of strings:
(
Numerical reading with units
Mode
Modifiers
)
'''
if string == None:
return None
bytes = [ord(i) for i in string]
# Byte 0: mode
modes = ("DC V", "AC V", "DC uA", "DC mA", "DC A", "AC uA",
"AC mA", "AC A", "ohm", "CAP", "Hz", "NET Hz", "AMP Hz",
"Duty", "Net Duty", "Amp Duty", "Width", "Net Width", "Amp"
"Width", "Diode", "Cont", "hFE", "Logic", "dBm", "EF", "Temp")
mode = modes[bytes[0]]
digits = [0, 0, 0, 0]
n = 4
for di, by in zip((3, 4, 5, 6), (3, 2, 1, 0)):
# Mask out the decimal point
byte = bytes[di] & (~8)
digits[by] = self.interpret_digit(byte)
# Get decimal point. If dp = 1, 2, or 3, this locates the decimal
# point after the first, second, or third digit, respectively. If
# dp = 0, there is no decimal point.
dp = 0
if bytes[3] & (1 << 3):
dp = 3
elif bytes[4] & (1 << 3):
dp = 2
elif bytes[5] & (1 << 3):
dp = 1
# Get sign
sign = ""
if bytes[7] & (1 << 3): sign = "-"
# Get units
units = self.GetUnits(bytes)
# Construct number
if dp: digits.insert(dp, ".")
number = (''.join(digits)).strip()
# Strip leading zeros
while number and number[0] == "0":
number = number[1:]
if not number:
number = "0"
if number[0] == "." or number[0] == "F" or number[0] == "P":
number = "0" + number
if number == "0.0F": number = ".0F" # Diode open case
# Make the reading
reading = sign + number + " " + units
return reading, mode, self.GetModifiers(bytes)
def DumpPacket(self, packet):
s = ""
for i in xrange(len(packet)):
s += "%3d " % ord(packet[i])
return s
def GetReading(self):
# Return a string representing a reading. If we could not get a
# reading, return None.
packet = self.get_packet()
if 0: # Turn on to see individual bytes
print self.DumpPacket(packet)
return self.InterpretReading(packet)
if __name__ == "__main__":
# Immediately start taking readings and printing to stdout. The
# number passed on the command line (if present) represents the wait in
# seconds between readings.
from time import strftime, sleep
from sys import argv
def TimeNow():
return strftime("%d%b%Y-%H:%M:%S")
com_port = 3
rs = RS22812(com_port)
interval = 1
if len(argv) > 1:
interval = abs(float(argv[1]))
count = 0
while True:
count += 1
r = rs.GetReading()
print TimeNow() + " [%d]" % count, r
sleep(interval)