Close

Does it work a million times?

A project log for TP4000ZC Serial DMM Adapter

USB adapter for cheap RS232 DMMs that translates into a "sane" serial protocol

ted-yapoTed Yapo 08/19/2016 at 02:180 Comments

The biggest problem I wanted to solve with this project was avoiding stale data in automatic measurements taken with these DMMs. Since the DMMs continuously stream data with no way to synchronize with a specific point in time, any buffering can result in reading old values. If you have a program setting inputs to a device-under-test, then trying to measure the device, old data can really screw up your results. It's bitten me a number of times. So, I took care to avoid causes of stale data when programming this adapter. The adapter waits for a command from the host before sending any results. Upon receiving the command, the adapter continually flushes the nano's serial receive buffer for 250ms, which is the rate at which the meter sends samples. This discards any samples which may have been taken before the command was received from the host. Only then does the nano start listening for the beginning of a 14-byte packet from the DMM. It sounds good, but does it work? Could there be buffering in the meter itself that defeats all my precautions? I decided to do some long-term testing (like a million samples from the meter) to see if I could catch it sending bad data.

Fake DUT

I had a teensy 3.2 sitting on my desk, so I decided to use it as a synchronized tester for the DMM: the teensy sets the level on a digital output pin (0/3.3V), which the DMM must then read back accurately for a number of trials. Code on the teensy listens for 3-character commands from a python program running on a PC. The code sets state of pin D2 based on the first character in the command, then echoes the command back to the python code. The last two characters of the command are a sequence number to ensure no commands are dropped. When the python code receives the echo, it knows the pin state has been set, so it requests a sample from the meter. If the voltage read from the meter doesn't indicate the pin state that the command requested, the python code flags an error. Otherwise, the python code prints a log of this trial and starts again. Each pin state is randomly chosen with equal probability, and a 0-100ms random delay is executed between trials to try to expose any subtle timing issues. The python code is also running on my desktop which I periodically hammer on during the day, so that should add some more varaibility to the test.

Here's the code on the teensy:

// DMM tester: set pin2 high or low on synchronized control over serial

int testPin = 2;

void setup()
{
  Serial.begin(9600);
  pinMode(testPin, OUTPUT);
}

// receive a string.  set or clear pin based on first char, then
//  echo the string back for synchronization
void loop()
{
  char buf[32];
  if (Serial.available() > 0){
    int len = Serial.readBytesUntil('\n', buf, 32);
    if (buf[0] == '1'){
      digitalWrite(testPin, HIGH);
    } else {
      digitalWrite(testPin, LOW);
    }
    Serial.write(buf, len);
  }
}

and the python code run on the PC:

#!/usr/bin/env python
#
# test serial DMM interface for buffering issues / stale data
#
import sys
import serial
import time
import random
import math
dmm = serial.Serial('/dev/ttyUSB3', baudrate = 9600, timeout = 1)
tester = serial.Serial('/dev/ttyACM2', baudrate = 9600, timeout = 1)
# wait for arduinos to reset
time.sleep(2);
count = 0
while True:
  count += 1
  if random.random() > 0.5:
    cmd = '1'
  else:
    cmd = '0'
  cmd += '%2d' % (count % 100)
  # output a random bit on pin D2 of tester, and wait for response for sync
  tester.write(cmd + '\n')
  response = tester.readline()
  if response != cmd:
    print 'Command error: (%s) - (%s)' % (cmd, response)
    sys.exit(-1)
  # read value from DMM and check if bit is correct
  dmm.write('n')
  value = float(dmm.readline())
  if ( (cmd[0] == '1' and value < 3.0) or
       (cmd[0] == '0' and value > 0.3) ):
    print 'Bad data error: (%s) - (%f)' % (cmd[0], value)
    sys.exit(-1)
  print '%d: (%s) (%s) (%f)' % (count, cmd, response, value)
  # sleep for 0-100ms randomly
  time.sleep(random.random() / 10)

Results

It's running now, producing output like this:

822: (022) (022) (0.001000)
823: (123) (123) (3.337000)
824: (024) (024) (0.001000)
825: (125) (125) (3.337000)
826: (126) (126) (3.338000)
827: (027) (027) (0.001000)
828: (128) (128) (3.337000)
829: (129) (129) (3.337000)

I'll post an update here when it either fails or reaches some impressive number of trials.

UPDATE 20160819:

35,000 mark passed with no failures. I need the meter for something else today, so I'm going to have to interrupt the test and start it over later.

UPDATE 20160820:

I didn't have the heart to stop it yesterday. As of this morning, 83,718 trials and no errors.

UPDATE 20160821:

I really have to interrupt this test now; I need both meters. It did 151,634 readings with no errors before I stopped it. I think I'm going to buy another meter for a long-term test, and vary the test code a bit more.

Discussions