Keithley 2000 & 2700 Data Logging in Python

ReadDMMs.py

This is a simple, braindead, python script to get measurements from a Keithley 2000 & 2700 DMMs using VXI-11 and store them in a simple timestamped SQLlite database.

It’s not a general tool, but it should be easy enough to tweak it to dump to a text file, take different measurements, use different ranges, different intervals, different communications transports.

It doesn’t have enough error checking, and bug fixing, but it mostly works well enough. It recovers from (some) malformed readings. It doesn’t recover from other errors (mostly communication related)

Requirements

  • OS X or Linux
    • It may work with Windows, but I haven’t tested it.
  • Python 2.7.x 
    • It may work with others, I haven’t tested it.
  • python-vxi11

How to use

The script takes voltage readings at ~10s intervals, on the 100v range, and stores them in a SQLlite3 DB called readings.sqlite3 in the current working directory. If you want different behavior, look through the source code and make the necessary changes.

You MUST edit the file to configure the name of your VXI-11 gateway and GPIB address(s) of the devices you want to poll.

Finally, run the script, ie:

python ReadDMMs.py

Tips

Since this script doesn’t do much error checking, it occasionally dies. In my experience, when it dies, it is because of a communcations timeout. In such situations, restarting the script is often enough for hours more logging.

I usually run it in a shell loop, so it restart automatically after a delay:

while true; do python ReadDMMs.py; sleep 150; done

If you modify this script to communicate with your Keithley 2000 DMM over RS-232 serial, be aware that a problems with hardware/firmware before ~2007 can result in frequent communication failures.

The simple workaround is to modify the RS-232 cable, or the DMM’s own RS-232 port, to ensure that the RTS pin (#7) is not connected.

# coding: utf-8

# ReadDMMs.py
# ===================================================
#
# This is a simple, braindead, python script to get measurements from a
# Keithley 2000 & 2700 DMMs using VXI-11 and store them in a simple
# timestamped SQLlite database.
#
# It's not a general tool, but it should be easy enough to tweak it to
# dump to a text file, take different measurements, use different
# ranges, different intervals, different communications transports.
#
# It doesn't have enough error checking, and bug fixing, but it mostly
# works well enough. It recovers from (some) malformed readings. It
# doesn't recover from other errors (mostly communication related)

# Requirements: 
# =============
#
# OS X or Linux. 
# It may work with Windows, but I haven't tested it.
# Python 2.7.
# It may work with others, I haven't tested it. python-
# vxi11
# Developed using 0.8 from here: 
# https://github.com/python-ivi/python-vxi11

# How to use: 
# =========== 
#
# The script takes voltage readings at ~10s intervals, on the 100v
# range, and stores them in a SQLlite3 DB called readings.sqlite3 in the
# current working directory. If you want different behavior, look
# through the source code and make the necessary changes.
#
# You MUST edit the file to configure the name of your VXI-11 gateway
# and GPIB address(s) of the devices you want to poll.
#
# Run the script, ie: 
# python ReadDMMs.py # 

# Tips: 
# ===== 
#
# Since this script doesn't do much error checking, it occasionally
# dies. In my experience, when it dies, it is because of a communcations
# timeout. In such situations, restarting the script is often enough for
# hours more logging. 
#
# I usually run it in a shell loop, so it restart automatically after 
# a delay:
# while true; do python ReadDMMs.py; sleep 150; done
#
#
# If you modify this script to communicate with your Keithley 2000 DMM
# over RS-232 serial, be aware that a problems with hardware/firmware 
# before ~2007 can result in frequent communication failures.
#
# The simple workaround is to modify the RS-232 cable, or the
# DMM's own RS-232 port, to ensure that the RTS pin (#7) is not
# connected.
#
# Details: http://techobsessed.net/2015/05/keithley-2000-rs-232-serial-wtf/


# Copyright (c) 2015, Erik Speckman (eas@techobsessed.net) All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# In[1]:

import sqlite3
import vxi11
import time


# In[2]:

gw = "ics.turnip" # IP number or DNS address

targets = (("K2.7K1", 1), #String is a name for your benefit, Number is GPIB address
 ("K2.7K2", 2))
 # ("K2K1", 11),
 # ("K2K2", 12),
 # ("K2K3", 13))

initialization_cmds = ("SYST:AZER:STAT ON", # Enable Autozero
 "UNIT:VOLT:DC V",
 "SENS:VOLT:NPLC 5", # Integrate 5 NPLC cycles (eq "slow" mode on 2700)
 "SENS:VOLT:DC:RANG 100", # Set DC volt measurements to 100v
 "SENS:VOLT:DC:RANG:AUTO OFF", # Turn off automatic ranging for DC volts
 "SENS:VOLT:DC:DIG 7", # Display maximum digits for DC volt readings (doesn't effect remote readings)
 "SENS:VOLT:DC:AVER:STAT OFF", # Turn off filtering for DC volt readings
 "SENS:FUNC VOLT:DC", # Tell DMM to measure DC volts
 "FORM:DATA ASCII",
 "FORM:ELEM READ, UNIT") # Specify data format for remote readings


reading_delay = 10.0 - 0.28 # seconds. I estimated that it takes ~0.28 seconds to read from my 5 meters.
instrument_timeout = 20


# In[3]:

def initialize(instruments, commands):
 for i in instruments.values():
 for c in commands:
 i.write(c)


# In[4]:

def ask_all(instruments, command):
 results = {}
 for name, i in instruments.items():
 results[name] = i.ask(command)
 return results


# In[5]:

def open_db(path="readings.sqlite3", table_name="readings"):
 import sqlite3
 
 # Options to connect method enable automatic conversion from/to python datetimes
 db = sqlite3.connect(path, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
 
 #create table, if it already exists, eat report exception and continue
 try:
 db.execute("""CREATE TABLE {}
 (Id INTEGER PRIMARY KEY AUTOINCREMENT,
 Instrument TEXT NOT NULL,
 Timestamp timestamp NOT NULL,
 Value REAL NOT NULL,
 Units TEXT )""".replace("\n", " ").format(table_name))
 print table_name, " created"
 except sqlite3.OperationalError, err:
 print err
 return db


# In[6]:

def insert_readings(connection, readings):
 import datetime
 import sqlite3
 import re
 
 p = re.compile('^(?P<reading>[-\+]?\d\.\d+E[-\+]\d+)(?P<units>\w+$)')
 
 ts = datetime.datetime.now()
 
 rows = []
 try:
 for name, reading in readings.items():
 m = p.match(reading.split(',')[0])
 d= m.groupdict()
 rows.append((name, ts, float(d['reading']), d['units']))
 
 # print rows
 connection.executemany("INSERT INTO READINGS (Instrument, Timestamp, Value, Units) VALUES(?, ?, ?, ?)", rows)
 connection.commit()
 
 # In case of malformed readings, print error, readings, and continue
 except AttributeError, err:
 print err
 print readings
 
 return




# In[7]:

instruments = {}

for name, address in targets:
 client_id = "gpib0, {addr}".format(addr=address)
 i = vxi11.Instrument(gw, client_id)
 i.timeout = instrument_timeout
 print address,": ", i.ask("*IDN?")
 instruments[name] = i


# In[8]:

initialize(instruments, initialization_cmds)


# In[9]:

db = open_db()


# In[10]:

# Show first readings to allow sanity check
readings = ask_all(instruments, ":SENS:DATA:FRESH?")
print readings


# In[11]:

counter = 0
start_time = time.time()
for c in range(1,1200000):
 readings = ask_all(instruments, ":SENS:DATA:FRESH?") # Ask for the most recent complete reading 
 insert_readings(db, readings)
 counter = counter+1
 if (counter%100 == 0):
 print "count:", counter, "duration:", time.time()-start_time, "s."
 time.sleep(reading_delay)
end_time = time.time()
print "duration:", end_time - start_time


# In[12]:

db.close()

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.