tmc - Test & Measurement Control ================================ Copyright (C) 2008 by OpenMoko, Inc. Written by Werner Almesberger All Rights Reserved This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. * WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * This program is still highly experimental. It can accomplish a limited number of tasks but may need manual device resets and such. Also, the Python binding is not smooth at all. (This is my first use of Python, so there's still a bit of learning to do.) * WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * WARNING * Concept and use =============== The tmc package contains a set of functions to communicate with test and measurement instruments. (Currently always using SCPI, but devices speaking different protocols could also be supported.) These basic functions are described below. On top of basic communication, tmc provides a rich set of abstractions and libraries with helper functions written in Python. The goal is to allow routine tasks to be expressed in a concise and understandable way, and to facilitate the construction of complex automated experiments. These libraries are not documented yet, but there is a number of annotated examples in the demo/ directory. Installation ============ Prerequisites ------------- To build TMC, development libraries for libc, Python, and libusb are required. On Ubuntu, one would install them with: # apt-get install libc6-dev python-dev libusb-dev At run time, some parts of TMC use NumPy, Tkinter, and ImageMagick. On Ubuntu, one would install them with: # apt-get install python-numpy python-tk imagemagick Waveforms are saved in a format compatible with gnuplot. On Ubuntu, one would install gnuplot with: # apt-get install gnuplot Building and installing TMC --------------------------- TMC is built by simply running "make": % cd tmc % make To install TMC on the local machine, run the following command as root: # make install System setup ------------ Access to USB TMC devices requires that the user running TMC has write access to the corresponding device files. This is accomplished either by using TMC from a privileged account (i.e., root), which is generally considered unsafe practice, or by making all these device files belong to a group "usbtmc" and making TMC users members of this group. The instructions below apply to Ubuntu 8.04. The procedure may differ slightly for other systems, and it will differ a lot for systems not using udev. The following command creates the "usbtmc" group, adds the permission information for USB TMC devices supported by TMC, and restarts udevd: # make system-setup To give users membership of the "usbtmc" group, run the following command for each user: # adduser username usbtmc After logging in (or requivalent), this user should then be able to access USB TMC devices. Low-level interface =================== This suite supports instruments that speak SCPI or a dialect thereof using one of the following transport protocols: - TCP, in a TELNET variant - USBTMC - SCPI over a TTY (serial port) Low-level communication is handled by a set of C functions. The system is controlled by a Python script. Instruments can operate in two modes: - synchronous: the control script sends commands and retrieves responses from the instrument. - asynchronous: the instrument sends a stream of data, which is timestamped and written to a file. The control script executes concurrently. Accessing an instrument ----------------------- Communication to an instrument is established by creating an instance of the tmc.Instr class: import _tmc instr = _tmc.Instr(transport, arg, ...) "transport" is the transport to use, "tcp", "usb", or "serial". "arg" is a (possibly empty) list of arguments for the transport protocol handler. (See below for details.) Synchronous mode ---------------- Commands are sent to an instrument with instr.send(command, ...) where "command" is one or more command strings. instr.send(command1, command2) is equivalent to instr.send(command1) instr.send(command2) An instrument response is retrieved with response = instr.read() Note that this function blocks until the complete response has been received from the instrument. Asynchronous mode ----------------- Asynchronous mode is entered with instr.start(filename [, command]) This creates the specified file and start streaming instrument output to the file. Instrument output is chopped into separate entries not only at message boundaries but also at each comma. Each entry is preceded by a timestamp (seconds.milliseconds) and ends with a newline. In asynchronous mode, instr.read cannot be used. However, messages can be send with instr.send, in particular: instr.send('*TRG') If the command argument is passed to "start", this command is issued at the end of a read to start another read cycle. Typically, this command will be 'READ?'. The command instr.stop() leaves asynchronous mode, closes the file, and sends a "device clear" (DCI) command to the instrument. Debugging --------- Debug output can be enabled or disabled with _tmc.debug(level) Level 0 turns debug output off. Transport arguments =================== tcp --- The TCP transport accepts the following arguments: ["tries=N",] hostname [, port] tries=N The number of times establishing the connection should be attempted. This works around instruments spuriously refusing a new connection, or (worse) resetting a connection on the first read. By default, only one attempt is made. hostname The name or IPv4 address to connect to. port The service name or port number to connect to. The default port number is 3490. usb --- The USBTMC transport accepts the following arguments: [bus=N,] [device=N,] [product=N,] [vendor=N,] [timeout=N,] [retry] bus=N The USB bus number. Default: any. device=N The number the device has been enumerated as. Default: any. product=N The USB product ID. Default: any. vendor=N The USB vendor ID. Default: any. timeout=N Timeout per USB operation. Default: infinite. retry Retry after an input timeout. serial ------ The serial transport accepts the following arguments: [bps=N,] [crtscts,] [echo,] [no_dci,] device bps=N The speed of the serial interface. Default: use the previous setting. crtscts Use RTS/CTS flow control: Default: turn it off. echo The instrument echos back commands. no_dci The instrument does not support the DCI function. device Device Instrument quirks ================= Fluke 8845A ----------- Accepts only one session at a time, and needs a brief pause between sessions. Attempts to connect too quickly yield "connection refused". Also, sometimes the connection gets reset when trying to read the first result from the device. Setting the option "tries=3" should work around all this. [ Note: no, it doesn't fix this all the time. See below. ] PicoTest M3500 -------------- When trying to use multiple internal triggers, only the first one is taken. It is thus not possible to stream an indefinite number of samples. Instead, PicoTest suggest to use a large SAMP:COUN and to re-issue READ? when running out of samples. One drawback of this work-around is that several samples are sent to the host en bloc and thus have the same timestamp. Therefore, the individual sample timing has to be reconstructed in this case. Leaptronix mPP-3040D -------------------- Speaks a weird little dialect of SCPI. The main quirks are that there is a response to each command, not only to queries, and that the colon that resets the command path is mandatory. Known bugs and missing features =============================== Only a small part of the error recovery procedures required by USBTMC are implemented. More of this will be added later. The USBTMC specification seems a bit unclear about the treatment of bTag, so it may be that just starting with 1 in each session actually violates the protocol. Error handling is almost entirely absent and not at all what Python expects to be done. Need to learn more about this. Checking argument syntax deep down in transport protocol drivers is ugly at best. Passing an argument that doesn't want to be a string yields a segfault. There should be a way to detect whether an asynchronous acquisition has completed, and also to synchronize with that event. The "telnet" protocol handler retries connections even if "tries" is set to one. (Fixed now ?) The Fluke 8845A resets the connection also on writes. Most likely, the first TCP segment after the initial SYN resets it. Should probably send an *IDN? to check. The way how we select the protocol is probably very non-Python-ish. Should there be a per-module method for each protocol that returns a properly initialized object of type tmc.Instr instead ? Or maybe even a set of subclasses that inherit tmc.Instr ? The latter sounds a bit pompous for what little we try to accomplish here. The USBTMC driver doesn't implement most of the error handling and. The USBTMC driver doesn't implement DCI properly, so the device sometimes has to be reset manually.