USBTMC driver for Mac

This post is part of USB Programming on Mac series. More posts in this series can be found here.

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
usbtmc_buffer[29]
usbtmc_buffer[30]
usbtmc_buffer[31]
USBTMC: Buffer content is:
usbtmc_buffer[0] = 01
usbtmc_buffer[1] = 01
usbtmc_buffer[2] = FE
usbtmc_buffer[3] = 00
usbtmc_buffer[4] = 11
usbtmc_buffer[5] = 00
usbtmc_buffer[6] = 00
usbtmc_buffer[7] = 00
usbtmc_buffer[8] = 01
usbtmc_buffer[9] = 00
usbtmc_buffer[10] = 00
usbtmc_buffer[11] = 00
usbtmc_buffer[12] = 3A
usbtmc_buffer[13] = 50
usbtmc_buffer[14] = 4F
usbtmc_buffer[15] = 44
usbtmc_buffer[16] = 31
usbtmc_buffer[17] = 3A
usbtmc_buffer[18] = 44
usbtmc_buffer[19] = 49
usbtmc_buffer[20] = 53
usbtmc_buffer[21] = 50
usbtmc_buffer[22] = 6C
usbtmc_buffer[23] = 61
usbtmc_buffer[24] = 79
usbtmc_buffer[25] = 20
usbtmc_buffer[26] = 31
usbtmc_buffer[27] = 0A
usbtmc_buffer[28] = 00
usbtmc_buffer[29] = 00
usbtmc_buffer[30] = 00
usbtmc_buffer[31] = 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[12]) and always ends with a 0x0A, a carriage return character, at byte 28 (usbtmc_buffer[27] 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 [0]:

…
usbtmc_buffer[12] = 3A
usbtmc_buffer[13] = 50
usbtmc_buffer[14] = 4F
usbtmc_buffer[15] = 44
usbtmc_buffer[16] = 31
usbtmc_buffer[17] = 3A
usbtmc_buffer[18] = 44
usbtmc_buffer[19] = 49
usbtmc_buffer[20] = 53
usbtmc_buffer[21] = 50
usbtmc_buffer[22] = 6C
usbtmc_buffer[23] = 61
usbtmc_buffer[24] = 79
usbtmc_buffer[25] = 20
usbtmc_buffer[26] = 31
usbtmc_buffer[27] = 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[27] = 0A –> Carriage return = \n
usbtmc_buffer[28] = 00 –> null byte for boundary padding
usbtmc_buffer[29] = 00 –> null byte for boundary padding
usbtmc_buffer[30] = 00 –> null byte for boundary padding
usbtmc_buffer[31] = 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:

Result of several SCPI commands sent to my Agilent DSOX 2002A via USB. :POD1:DISPlay 1, :DISPlay:ANNotation:BACKground OPAQue, :DISPlay:ANNotation:TEXT, DISPlay:ANNotation:COLor RED.

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.

4 Comments to USBTMC driver for Mac

1. Paul says:

Hi,

Nice work! I already saw you tell someone else you wouldn’t be releasing the code until it’s ready. Can I prod you again for anything, any snippet? I just bought a Rigol 1054Z and part of my day job is talking to nice, world-class T&M instruments over nice interfaces. I’d so love to be able to make talking to my own Rigol a personal project, and maybe even advance the Mac USBTMC scene a tiny bit.

Paul

• AP says:

Hi, Paul, thank you for your comments. Meanwhile I took a break from updating the blog due to some other job assignments. Simply didn’t have enough free time for the blog. I will, though, and let you know.
Warmest regards,
AP

2. Tj says:

Hello!

Like Paul, I’m going to beseech you to please provide a snippet of what you have working. I’m trying to calibrate an AD5933 with an Agilent LCR and I need to communicate with the LCR over USBTMC with my Mac.

The source for the stuff I’ve done is here: https://github.com/WuMRC/drive

• AP says:

Hi, TJ, sorry for being so late on this. A busy, busy year.