Friday 25 December 2009

Reverse engineering a multimeter's language

Some time ago I got myself checking all the gadgets in our lab, to see which ones can I connect to a computer. This is mostly because I like our gadgets and I like our computers, but together they are just that more awesome... Been checking oscilloscopes, function generators, counters, even high power lasers. Most of them seemed to have a decent (well, at least usable) manual, so I could do some basic communication right away, until I come up with some more complete awesome plan to automate the lab. But there are always exceptions to the rule, even if tiny ones.



There was this Pro's Kit 3PK-345 Digital Multimeter (pictured on the right), that could do RS-232 serial communication. Of course, this is not the most crucial equipment of them all, but could be quite useful for rough-and-ready monitoring and logging, since it has so many different measurement functions.

The problem is that there's no clue in the manual, how to communicate with it, what are the settings, what's the "language", are there any quirks... There was, however, a little piece of monitoring software on an attached CD-ROM, that was able to talk the multimeter. But it was a very basic piece of garbage program. I think it was really just thrown together, and I base this theory on the fact that the program icon is the default Delphi project icon (that one on the left) - I know it because used it like 10 years ago for a while (oh, my, even for some pocket money, but that's another post). And if you don't bother to change the icon, I don't have much confidence in the rest of the code...

But at least it worked. At least there was an example, how I should do it, and could set the hounds serial sniffers on it and check out what are those two talking about... That was another adventure, serial sniffer... Tried quite a few under Windows until found the right one giving all the information I needed. But after all the testing I don't even remember what it was, maybe an evaluation copy of a paid software.

From the sniffing, I got the initialization:

Baud rate 600
RTS off
DTR on
Data bits=7, Stop bits=2, Parity=None
Set chars: Eof=0x00, Error=0x00, Break=0x00, Event=0x00, Xon=0x11, Xoff=0x13
Handflow: ControlHandShake=(DTR_CONTROL), FlowReplace=(), XonLimit=0, XoffLimit=4096
DTR on
RTS off
DTR on
RTS off

That tells me most of the settings, plus that the multimeter needs that few extra DTR/RTS cycles (indeed, without those I couldn't get it to talk). Another thing the sniff told me, that multimeter works in request/reply mode, that is I have to initiate a reading by sending the sequence "D\r" (\r is the "carriage return") to the device to which it will reply.

Now, let's get down to business. Using PySerial, it is very easy to set things up:
ser = serial.Serial('/dev/ttyUSB0',baudrate=600, bytesize=7, stopbits=2, \
       parity=serial.PARITY_NONE, timeout=1, xonxoff=1, rtscts=0, dsrdtr=1)
ser.setDTR(1)
ser.setRTS(0)
ser.setDTR(1)
ser.setRTS(0)
ser.setDTR(1)
ser.setRTS(0)
line = ser.readline()

Notes:
  1. This is on Linux, but works on Windows too by replacing /dev/ttyUSB0 with COMx, where x=1,... the appropriate serial port number
  2. I just have to write "D" to the port, because it will automatically attach the carriage return
It's time to decipher the language. Trying the different settings, the multimeter's reading is returned as a 13 character string.

  • First two characters tell the type of the reading, though one needs the unit (last part of string) to completely determine the type for current and voltage measurements. It's followed by a space
  • Five characters contain the reading value (signed).
  • Last 1-4 characters tell the units of the measurement.
The more or less complete catalog is the following:

### Output (13 chars) and Line ending (+1 not shown: \r /carriage return,0x0D/)
DC -0.000   V  (DC Voltage)
AC  0.000   V  (AC Voltage)

OH   O.L MOhm  (Resistance, ohm mode, no connection)
OH  0.008kOhm  (kOhm measure)
OH  080.8 Ohm  (Ohm measure
OH   OL.  Ohm  (Resistance, short mode)

DI    OL   mV  (Diode)

    0000       (hFE /Forward Current Gain/ mode)

TE -  OL    C  (Temperature, without connection)
TE  0024    C  (Temperature, with thermocouple)

CA  0.011  nF  (Capacitor, 4nF scale)
CA  000.3  nF  (400nF scale)

DC -0.000  mA  (DC current, 4mA scale)
DC -000.0  mA  (400mA scale)
DC -00.00   A  (10A scale)
AC  0.000  mA  (AC current, 4mA scale)
AC  000.0  mA  (400mA scale)
AC  00.00   A  (10A scale)

The main annoying thing I found, that there are a number of different notations for "over limit" or invalid values, such as "O.L", "OL.", and "OL". And here it is a "capital-O", not a "zero", thus it needs extra checking in the code, not just simple conversion. Nevertheless....

I wrote to the company as well to ask for some inside info on this "reverse-engineered" language, and they are promised to give me a complete reference. Yeah, "getting back" to me since the end of August. Fortunately they help is probably not needed anymore, I just wanted to double-check it, and also to test them how do they respond to such request. Initial impression was very favorable, but that has kinda decayed since then...

Now, because the language is so simple, it is very easy to implement the code "speaking" multimeter. A very bad version Python version can be found in my GitHub repo, where I keep all the other hardware related stuff as well. I seriously should get some Bad Code Offset for this one, but at least it works so far.