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.
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.
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.
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
CURVE? are the same thing.
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.
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:
- It would be easy to support multiple scopes; just instantiate more objects
- It is also easy to provide the same abstracted interface to a different scope
- 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].
Source : https://hackaday.com/2023/11/21/how-to-talk-to-your-scope/