How To Talk To Your Scope

It used to be only high-end test equipment that had some sort of remote control port. These days, though, they are quite common. Historically, test gear used IEEE-488 (also known as GPIB or, from the originator, HPIB). But today, your device will likely talk over a USB port, a serial port, or a LAN connection. You’d think that every instrument had unique quirks, and controlling it would be nothing like controlling another piece of gear, especially one from another company. That would be half right. Each vendor and even model indeed has its unique command language. There has been a significant effort to standardize some aspects of test instrument control, and you can quickly write code to control things on any platform using many different programming languages. In a few posts, I will show you just how easy it can be.

The key is to use VISA. This protocol is defined by the IVI Foundation that lets you talk to instruments regardless of how they communicate. You do have to build an address that tells the VISA library how to find your device. For example: “TCPIP::192.168.1.92::INSTR.” But once you have that, it is easy to talk to any instrument anywhere.

I say that thinking it is a problem is half right because talking to the box is one task of the two you need to complete. The other is what to say to the box and what it will say back to you. There are a few standards in this area, but this is where you get into problems.

Rigol

The Rigol web interface is just a duplicate of the touchscreen

Way back in 2015, I picked up a Rigol DS1052E and wanted to play with the remote control via USB. The resulting code (on GitHub) uses Qt and expects to open a USB port. The command language is simple and, helpfully, includes commands that simulate pressing keys on the front panel. I would find out that my latest Rigol scope, a DHO924S, doesn’t provide those KEY commands nor does my DS1154z.

So, for a 2023 version, I wanted to abstract some of the complexity away to simplify a future switch between different scopes. I was watching a video review of a DHO900-series scope, and the reviewer complained that while the scope would show its screen via a web interface (and you can operate it via that virtual touchscreen), he missed having access to the knobs next to the screen when using the scope remotely. I realized it would be possible to write a small program to control the scope and provide the “missing” controls as a panel that could sit next to the scope’s web interface.

Python

Since I also wanted to use this in scripts and I was freshly off the Hackaday Supercon badge, which used MicroPython, I decided to do this all in Python. My general plan of attack was simple:

  • Use VISA to connect to the scope
  • Abstract the direct SCPI commands using Python methods or properties in a class that represents the scope
  • Create a GUI to use adjacent to the web interface

This post is about the first two items. I’ll cover the GUI next time. However, I didn’t totally wrap the entire SCPI command set in the Python class. Instead, I just provided what I needed for the task at hand. As you’ll see, it is easy to add whatever you like.

PyVisa

The first order of business was to install PyVisa. This sounds like it would be all you need, but it isn’t. Instead, it is a wrapper around a “real” VISA backend. Two popular choices are NI-VISA from National Instruments (free, although you probably have to register) or the open-source pyvisa-py. Try this:

pip3 install pyvisa pyvisa-py

Note that, as of now, pyvisa-py supports TCP/IP, GPIB, USB, and serial. That covers most bases, but if you need something different, you may have to try a different backend.

When you install pyvisa, it should leave a binary (in ~/.local/bin for me) called pyvisa-info. Run that, and it will tell you if it finds backends and will also tell you other things you might need to install. For example, I have no GPIB bus instruments and didn’t install that driver, so it warns me that I should install it if I want to talk to GPIB devices. I don’t, so I’m good. If you get bad error messages from pyvisa-info, you should fix them before you try to go further.

While SCPI defines some commands, mostly it is hit-and-miss. For example, a Tektronix scope might use CURVe? to get waveform data, while a Rigol might use WAV:DATA? , and other brands might only need DATA?. There are a few things you can usually count on, though.

When you see a question mark (like :WAV:DATA?) you are expecting the scope (or whatever it is) to answer you. When you don’t see a question mark, you are just sending data. In addition, when you see something like CURVe? written down, that means you are allowed to omit the “e” which saves a little time and communication bandwidth. So CURV? and CURVE? are the same thing.

Using PyVISA

To open a connection to a scope in Python, you will first need to import pyvisa. After that, you’ll create a resource manager and pass it a special open string. Here’s an excerpt from my scope abstraction class (find everything on GitHub):

# do the connect
def connect(self,usb,cxstring):
   if usb==0:
      cxstr="TCPIP::"+cxstring+"::INSTR"
   else:
      cxstr="USB0::0x1AB1::0x044C::"+cxstring+"::INSTR"
   self.visa=pyvisa.ResourceManager('@py')
   self.scope=self.visa.open_resource(cxstr)
   self.connected=True

In this case, I’m using a known scope, so I assume the USB IDs will be the same. If you are using a different instrument, this will change. Helpfully, if you open the Rigol’s web interface, you’ll find both connect strings written down for you to copy.

The information screen shows connection strings for USB and TCP/IP

The connect member function takes just the IP address or scope name and fills in the rest. The usb flag determines if it treats the input as an IP address or a name. The resource manager argument tells the library which “backend” to use. In this case, I’m using pyvisa-py (@py). If you omit the @py string, the library will use the IVI library which will work with backends like NI-VISA and other vendor-specific libraries. If it can’t find an IVI library it will fall back to using pyvisa-py. You can also pass a full path name to the library you want to use. If you prefer, you can set the PYVISA_LIBRARY environment variable instead. There is also a .pyvisarc file you can use for configuration.

May I See Some ID?

Nearly every instrument supports the query *IDN? to return an identification string. The code defines two methods to send data to the instrument. The write method sends data with no return. The query method gets a return and chops off the trailing new line:

# send a query and return a response
def query(self,string):
   s=self.scope.query(string)
   if isinstance(s,str):
      return s[0:len(s)-1]
   else:
      return s

# just send
def write(self,string):
   return self.scope.write(string)

# get ID
def id(self):
   return self.query("*IDN?")

Once you have that, the rest is just writing little wrapper functions. The only real problem is that the scope doesn’t offer — that I could find — any way to simulate a front panel key. However, some of the front panel keys behave differently depending on other modes. For example, each click of a knob might represent a different value when the scope is set on a slow sweep vs a fast sweep. Or, for another example, the trigger level knob doesn’t have a corresponding SCPI command. Instead, you must set the level for the specific trigger you want to use. That means you must know what triggering mode is set since each one uses a different command to set the level.

If you are writing a script, this isn’t a big problem because you can just set the triggering mode to a known value before you set the level correctly. But for a user interface, it is a problem because you have to write code to emulate the scope’s behavior. Or you can simply do what I did. Make a reasonable assumption and live with it.

The Result So Far

Just as a test, I wrote a little script at the bottom of rigol_scope.py to connect to a fixed IP address (since this is just a test) and put the scope in single mode every 10 seconds.

if __name__=="__main__":
   import time
# test script
   scope=Scope()
   scope.connect(0,"192.168.1.92")
   while True:
      print("here we go again...")
      scope.single()
      time.sleep(10)
      print(scope.trig_status())

The next step is a GUI, but that will wait until next time. However, writing the code as a class like this has at least three advantages:

  1. It would be easy to support multiple scopes; just instantiate more objects
  2. It is also easy to provide the same abstracted interface to a different scope
  3. You can use the object in other scripts or programs without having to carry along GUI code

Besides that, you could even support multiple GUIs with the same object. Anything you enhance or fix in the scope object benefits all the programs.

This isn’t the first time we’ve looked at PyVISA. Or, for that matter, SCPI.

Featured image: The delightfully named “Sine wave 10 kHz displayed on analog oscilloscope” by [Pittigrilli].

21 thoughts on “How To Talk To Your Scope

    1. I did that for the old 1052 or whatever it was. I had a little script that pulled the image and uploaded it to my webserver in case the world wanted to see what I was measuring that minute of the day ;-) I don’t think I preserved that anywhere, but it was good fun.

  1. From my experience the key to succes it not to use visa-library because it is bloatware.
    Just open an TCP-IP socket and then:

    sprintf(buf,”*IDN?\n”);
    write(s,buf,strlen(buf));

    And surprise, the devices will answer.

    Olaf

      1. No, I also use it with succes with my old Keithley 199 with this device:
        fopen(“/dev/usbtmc0”, “r+”)

        //Messbereich auf 300V einstellen
        sprintf(command,”R4″); printf(“Sende:%s\n”,command); fflush(stdout);
        fprintf(in_file,”%sX\r\n”,command); fflush(in_file);

        Works good after some fiddling, because the old 199 has a little
        bit strange implementation of GPIB.

        I use this adapter:
        https://github.com/xyphro/UsbGpib

        Works realy good!

        Olaf

        1. Thank you for sharing your exorcism experiences on the USB Realm, Father Olaf! They will be very useful to cast away the second Evil spirit that possessed a Signal Generator, now that its twin brother at Ethernet have been expelled by the SCPI spell recited on a Telnet session.

  2. I need to learn this stuff to write an app to adjust and calibrate my armarda of Tek TDS500/600/700 series scopes so I don’t need to heep an old PC with ISA bus working with the specific NI ISA GPIB card with the specific settings and the specific ‘Field Adjustment Software’ (FAS) software….

    The trick is that unlike the TDS200 and THS700 series scopes, all the calculations for the cal constants is done on the PC, so that needs to be reverse engineered too, and I have little to no experience in software.

    It’s somewhere way down there on the to-do list……..

    If anyone has any knowledge of this, feel free to visit the eevblog thread. :)

    https://www.eevblog.com/forum/testgear/tektronix-tds-scope-field-adjustment-software-reverse-engineering/

  3. The magical word missing is SCPI, which kind of define how most commands are structured.

    It also have defined common command like *IDN. VISA also defines common calls for three or four device classes. The driver then just map the commands!

    1. SCPI comes up several times. As mentioned, though, there are various irregularities and exceptions per vendor and sometimes model. Hard to do anything complex for multiple device types without some tweaking for each one.

      1. course, you are right! I just couldn’t formulate my thoughts correctly, I meant give a hint to IVI.
        For me, you misunderstood the use case for SCPI, it gives some general commands for data flow, and then it tries to be as close as possible to the menu structure of the instrument.

        In your example, for Tektronix you use Curve because there is a direct button on the device, for rigol you need to navigate to the submenu. In this way it is easy to implement a workflow, that you normally do by hand. And this is way porting it takes some quirks, as the workflow also changes.

        In comparison, IVI define device classes that have standards calls, you need to install the right driver to communicate with the instrument, but after that they are interchangeable.

    1. You could have it hooked up to test points on on the Device Under Test (DUT), and send commands to the DUT (or just have it running) and have the o-scope monitor the test points and when predetermined parameters are met, the o-scope transmits the data to the computer for recording the event. You could even have it stop sending when the events subside.

    2. If you document anything that’s an easy answer. In the old days we had the Polaroid gun cameras and boxes of type 95 film. Now I just screen shot the window. Also nice when you want to use your scope for data acquisition for some other processing. And, of course, automating tests. Which, I guess, if you are just doing your thing by yourself, don’t want to tell anyone else about it, and won’t make any more isn’t very interesting.

  4. You might want to look at PyMeasure. This is a suite of classes that wrap VISA instruments in classes that are easier to use than raw VISA, as is done in the article, but it contains a lot of drivers for various classes of instruments, so you’re not reinventing the wheel. I looked for a library like this for a long time and it seems about the best available.

    It also has facilities for running and graphing experiments, which is very useful.

    Also check out lxitools, which I very handy for debugging.

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

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