After some head banging, I finally managed to send SCPI commands from my Mac to my Agilent (Keysight) DSOX2002A. I’ve worked on this since december last year. Almost a year, but I did not spent a sustained amount of effort. I did it just during my free time. I still have a day job that requires most of my time and focus, cannot afford too much time for my hobbies (unfortunately). Yes, programming is a hobby. I am not making a living out of it (despite some opinions).
But I did it ! It was mostly an ambition I had. I was so pissed off when I realized that there are absolutely no OS X drivers for Agilent’s tools that I decided to make my own drivers and applications for Mac. This is how my console looks when the scope is issued a
:POD1:DISPlay 1 SCPI command:
Agilent Technologies Pipe ref 1: Bulk OUT Pipe ref 2: Bulk IN Pipe ref 3: Interrupt IN Enter a SCPI command… USBTMC: usbtmc_write called USBTMC: Can send remaining bytes in a single transaction… USBTMC: setup I/O buffer for DEV_DEP_MSG_OUT message… USBTMC: Instrument command: :POD1:DISPlay 1 USBTMC: Append write buffer (instrument command) to USBTMC message… USBTMC: Check if this is the last transfer… USBTMC: n_bytes: 29 USBTMC: this_part: 17 USBTMC: Add zero bytes to achieve 4-byte alignment… n_bytes: 32 Added 0x00 for: usbtmc_buffer Added 0x00 for: usbtmc_buffer Added 0x00 for: usbtmc_buffer USBTMC: Buffer content is: usbtmc_buffer = 01 usbtmc_buffer = 01 usbtmc_buffer = FE usbtmc_buffer = 00 usbtmc_buffer = 11 usbtmc_buffer = 00 usbtmc_buffer = 00 usbtmc_buffer = 00 usbtmc_buffer = 01 usbtmc_buffer = 00 usbtmc_buffer = 00 usbtmc_buffer = 00 usbtmc_buffer = 3A usbtmc_buffer = 50 usbtmc_buffer = 4F usbtmc_buffer = 44 usbtmc_buffer = 31 usbtmc_buffer = 3A usbtmc_buffer = 44 usbtmc_buffer = 49 usbtmc_buffer = 53 usbtmc_buffer = 50 usbtmc_buffer = 6C usbtmc_buffer = 61 usbtmc_buffer = 79 usbtmc_buffer = 20 usbtmc_buffer = 31 usbtmc_buffer = 0A usbtmc_buffer = 00 usbtmc_buffer = 00 usbtmc_buffer = 00 usbtmc_buffer = 00 USBTMC: End buffer content. USBTMC: store bTag (in case we need to abort)… USBTMC: increment bTag — and increment again if zero… USBTMC: Incremented bTag = 2 Program ended with exit code: 0
When SCPI instructions are sent, these must be wrapped by the transmission routine in a
REQUEST_DEV_DEP_MSG_OUT wrapper. This is a requirement of the USBTMC USB488 subclass specification. I had many issues with this until I managed to get it right. Again, the documentation is unbelievable difficult to find. You might think that usb.org should have it ? Well, good luck finding it on their site. When googling for “USBTMC USB488 Subclass Specification”
or, at least, “USB488”, one of the first results is a link to a repo of the Physics Department at the University of California, San Diego. You have to be kidding me ! Universities still the best at this. (you might also want to check this link).
The user instruction (SCPI command) always starts at byte 13 (
usbtmc_buffer) and always ends with a
0x0A, a carriage return character, at byte 28 (
usbtmc_buffer in the above example). It is important to have a
0x0A that terminates the user instruction otherwise the instrument will return a Query Interrupted error. Don’t forget the +1 shift in numbering due to the fact that the buffer starts at index :
… usbtmc_buffer = 3A usbtmc_buffer = 50 usbtmc_buffer = 4F usbtmc_buffer = 44 usbtmc_buffer = 31 usbtmc_buffer = 3A usbtmc_buffer = 44 usbtmc_buffer = 49 usbtmc_buffer = 53 usbtmc_buffer = 50 usbtmc_buffer = 6C usbtmc_buffer = 61 usbtmc_buffer = 79 usbtmc_buffer = 20 usbtmc_buffer = 31 usbtmc_buffer = 0A …
The first 12 bytes contain the
REQUEST_DEV_DEP_MSG_OUT header. The total number of bytes must be divisible by 4. When commands do not achieve this, there should be a rounding procedure to the closest upper multiple of 4, that set nulls for the additional bytes (in order to preserve the multiple of 4–byte boundary alignment):
… usbtmc_buffer = 0A –> Carriage return = \n usbtmc_buffer = 00 –> null byte for boundary padding usbtmc_buffer = 00 –> null byte for boundary padding usbtmc_buffer = 00 –> null byte for boundary padding usbtmc_buffer = 00 –> null byte for boundary padding; total: 32 bytes -> divisible by 4
I dug into the only available decent piece of USBTMC open-source, the Linux driver made by Stefan Kopp. The usbtmc_read and write routines were adapted for OS X and included in my client–space application. For now, the application’s main entry point uses a static char to pass all commands for tmc488 wrap–up and further on the bulk–out USB pipe towards my scope. See below:
char text = ":POD1:DISPlay 1\n"; //char text = ":SAVE:IMAGe:FORMat PNG\n"; //char text = ":SAVE:IMAGe:STARt somefile.png\n"; //char text = ":DISPlay:ANNotation:BACKground OPAQue\n"; //char text = ":DISPlay:ANNotation:TEXT 'This is an Agilent DSOX2002A... and I have managed to control it from my Mac... with a custom-developed USBTMC driver...'\n"; //char text = "DISPlay:ANNotation:COLor RED\n"; //char text = "DISPlay:VECTors 0\n"; uWrite(text, sizeof(text));
And this is the result of the
:DISPlay:ANNotation:TEXT command plus several other (see above). This is how it looks on Agilent’s screen:
All this work was a bit of a nightmare. Luckily, I was inspired enough not to quit when I went through the most difficult moments, like nothing seemed to be right and working. I just left the project to rest for a while and went back to it when I had enough sleep or energy. However, despite the scarcity of the prototype’s functionalities, this is a major success for me. The simple fact that I was able to implement from scratch an USB communication protocol on a different platform (OS X) than the mainstream (Windoze), without having any examples or previously–released web–discoverable projects — this is big for me. My satisfaction is huge. This projects open some new opportunities for porting SCPI on Mac and having various libraries and code snippets and many other Open–Source projects for the entire community of enthusiasts.
That’s all for now. Next step is to refine the client–side application and make it work with commands sent from terminal (parsing
scanf probably). After I cross–check that all’s ok with the read and write routines from client–space, I will port these in a pure serial driver that will create entry points in
/dev. I believe it will be much easier to work with a POSIX file because it can also be accessed from applications like screen or CoolTerm etc. I will share the drivers when ready but, beware, use it on your own risk.