SCPI: On Teaching Your Devices The Lingua Franca Of Laboratories
One could be excused for thinking sometimes that the concept of connecting devices with other devices for automation purposes is a fairly recent invention. Yet for all the (relatively) recent hype of the Internet of Things and the ‘smart home’, laboratories have been wiring up their gear to run complicated measurement and test sequences for many decades now, along with factories doing much the same for automating production processes.
Much like the chaotic universe of IoT devices, lab equipment from different manufacturers feature a wide number of incompatible protocol and interface standards. Ultimately these would coalesce into IEEE-488.1 (GPIB) as the physical layer and by 1990 the first Standard Commands for Programmable Instruments (SCPI) standard was released that built on top of IEEE-488.
SCPI defines (as the name suggests) standard commands to interact with instruments. It has over the past decades gone on to provide remote interaction capabilities to everything from oscilloscopes and power supplies to exotic scientific equipment. Many off the shelf devices a hobbyist can buy today feature an SCPI interface via its Ethernet, USB or RS-232C port(s) that combined with software can be used to automate one’s home lab.
Even better is that it’s relatively straightforward to add SCPI functionality to one’s own devices as well, so long as it has at least an MCU and some way to communicate with the outside world.
Reinventing the Wheel Is No Fun
As much fun as it is to come up with one’s own communication standard for a custom widget, there is a lot to be said for sticking to existing standards, instead of adding another ‘standard’ to the pile. One good reason is the time you will spend on coming up with a protocol that works, which covers all of the edge cases and leaves enough room for future expansion when new features are added.
Another reason is that of compatibility with existing software, which also touches on why end users likely to be enthused about this awesome new protocol. When using SCPI, it can be fairly painlessly integrated into existing (lab) automation software, as the whole concept behind SCPI is that each instrument will implement its own range of custom commands in addition to a number of required ones.
For users of software like LabVIEW or Sigrok, the ideal scenario is that the device speaks SCPI, and that in the worst-case a custom handler has to be written for the custom SCPI commands when one isn’t available yet. What will never change here is the basic SCPI syntax, allowing for rapid bootstrapping of new devices, the prevention of bugs (no parser is perfect) and reusing of code. SCPI’s base command set also enables functionality like synchronization mechanisms by default.
Despite this, when yours truly looks at the current stack of measurement gear and programmable power supplies piled up in the home lab, not all of them speak SCPI. The Rigol DS1104Z oscilloscope does via its Ethernet port. The little brother of the Owon XDM2041 DMM (XDM1041) speaks SCPI via its USB port. So far so good, but then the electronic load (Arachnid Labs Reload Pro) speaks a custom protocol via USB that could have been SCPI.
The Manson HCS-3304 programmable power supply does the same thing with yet another custom protocol, with the commands listed in the manual revisions apparently also often being wrong. With only some of these devices supported by Sigrok at this point, automating tests would involve hacking my own decoder together, rather than a bit of high-level fumbling with custom SCPI device commands.
Using standards appropriately can save a lot of time, sanity and grey hairs. Which leads to the next question: just how easy is it to add SCPI to one’s own Awesome Widget?
Enter LibSCPI
Nobody wants to write their own SCPI parser from scratch, which is why the SCPI parser library v2, or simply ‘libscpi‘ is a good start. It implements the currently SCPI 1999 standard. Since we’d be interested in using SCPI on an embedded device, we’ll take a look at the provided FreeRTOS with LwIP (netconn) example. This shows the implementation of an SCPI server which runs in a FreeRTOS thread.
The SCPI server sets up a TCP listening port on the standard SCPI port (5025), after which commands can be sent to the device via any Telnet client or comparable in raw mode, i.e. plain text. Note that in this server example, LwIP netconn’s NETCONN_COPY
is used instead of NETCONN_NOCOPY
. This is essential to preventing data corruption (use of buffer data after delete) when using chained SCPI commands.
To use libscpi with a USART or other interface, the first thing to do is set up the library by calling SCPI_Init
. The following methods must also be implemented in your code:
SCPI_Write(scpi_t* context, const char* data, size_t len)
SCPI_Flush(scpi_t* context)
SCPI_Error(scpi_t* context, int_fast16_t err)
SCPI_Control(scpi_t* context, scpi_ctrl_name_t ctrl, scpi_reg_val_t val)
SCPI_Reset(scpi_t* context)
These functions are mostly self-explanatory. As can be ascertained from the example implementation, SCPI_Write allows libscpi to write to your output of choice, with SCPI_Flush used to flush any output buffers that may exist. SCPI_Error prints error messages from libscpi, SCPI_Reset resets the device, and SCPI_Control is used to write to the control channel (optional feature, here on TCP port 5026).
To get libscpi to parse any fresh incoming strings (always terminated with a newline, \n
, or \r\n
), your code calls SCPI_Input
, or in the case of singular commands, SCPI_Parse
can also be used directly.
An example implementation of libscpi on STM32 with the ST HAL alongside FreeRTOS and a simple HTTP server can be found in this GitHub repository. This targets the Nucleo-F746ZG development board.
SCPI Digital Multimeter
Also provided with the libscpi example is the example implementation of a digital multimeter device. If we open the command definitions and implementations in scpi-def.c, it gives us a good glimpse at what a custom device implementation would require. This starts with the command table, called scpi_commands
.
This table defines all commands in the format of a pattern with associated callback (all but the core ones implemented in the same file), starting with the IEEE mandated commands, e.g. *CLS (CLear Status):
{ .pattern = "*CLS", .callback = SCPI_CoreCls,}
The ‘*’ (asterisk) in front of a command means that it is a required, common SCPI command that every device must implement. Important ones are *IDN?
, which queries (note the question mark) the device about its identity, *RST
to command the device to reset and *WAI
that tells the device to wait with executing any new commands until the commands preceding this command have been completed.
After this block of required commands, we get the block with the DMM functions:
{.pattern = "MEASure:VOLTage:DC?", .callback = DMM_MeasureVoltageDcQ,}, {.pattern = "CONFigure:VOLTage:DC", .callback = DMM_ConfigureVoltageDc,}, {.pattern = "MEASure:VOLTage:DC:RATio?", .callback = SCPI_StubQ,}, {.pattern = "MEASure:VOLTage:AC?", .callback = DMM_MeasureVoltageAcQ,}, {.pattern = "MEASure:CURRent:DC?", .callback = SCPI_StubQ,}, {.pattern = "MEASure:CURRent:AC?", .callback = SCPI_StubQ,}, {.pattern = "MEASure:RESistance?", .callback = SCPI_StubQ,}, {.pattern = "MEASure:FRESistance?", .callback = SCPI_StubQ,}, {.pattern = "MEASure:FREQuency?", .callback = SCPI_StubQ,}, {.pattern = "MEASure:PERiod?", .callback = SCPI_StubQ,},
The reason for the mixed upper and lowercase use in the commands has to do with the ‘pattern’ aspect: in SCPI only the uppercase part of the pattern is required, and the lowercase section of a command can be omitted for brevity. As noted earlier, a command followed by a question mark is a query. The use of colons is to separate the levels of the tree hierarchy that defines an SCPI interface.
To use this hierarchy to measure voltage and current for DC in a single string, one would use the following command:
MEASure:VOLTage:DC?;:MEASure:CURRent:AC?
The semi-colon separates individual commands, and the leading colon resets the hierarchy to the root of the command tree. This latter feature can be used to create very brief concatenated command strings for e.g. measuring both AC & DC voltage:
MEASure:VOLTage:DC?;AC?
Since the first command left us in the VOLTage
hierarchy level, the subsequent command would trigger the AC?
query. This means that a well-designed interface for a device can make controlling it quite efficient even when manually typing in queries by avoiding unnecessary repetition.
Advanced Features
All of this merely scratches the surface of what SCPI is capable of, of course. In addition to plain text output, numeric strings can also be marked as being hexadecimal (#H), as octal (#Q), or as binary (#B). Arguments can be provided along with commands separated by a space. With libscpi these arguments can then be retrieved in the callback function.
Complex sequential and overlapping command sequences can also be set up using the *OPC
and *WAI
commands, along with the *OPC?
query. These can be used in combination with the status register commands and any device-specific commands to control specifically timed sequences.
The best part about SCPI is probably that it scales along with the complexity of the device, whether it’s a simple voltage meter, or a scientific instrument reading quantum-level perturbations, the underlying protocol does not change. By not having to reinvent the same basics every single time, the developer and user of the device can instead focus on the things that matter.
In the case of the end-user, that is probably the experience of unpacking the device, plugging it in and programming in the SCPI sequences that make it perform the desired functions. Which is the major benefit of following established standards.
[Heading image: Back of Rigol DS1104Z oscilloscope with the Ethernet and USB ports visible. Credit: Rigol]
Post a Comment