What Cascade does · USB & APIB

The USB–APIB link, byte by byte

Chris asked for this page: "I would like a page in The Correction Log about how the app communicates with the instrument and the USB-APIB interface. there is a page in tehre now, but it does not cover technical details about the USB protocol translating the commands and things like that> I would like to understand that more fully. One of the reasons i am interested in this, is that there may be a way to make an APIB to Ethernet interface. Companies like Prologix have done this with GPIB controllers, for instance."

Everything below comes from three places, and each statement says which: the app's code (Cascade/Cascade/APDevice.swift), USB captures of APWIN (T92, a three-minute capture of 111,118 commands, is used for the counts and timings), and AP's manuals. Where something is a guess, it says so.

The chain

Cascade (macOS)  ──libusb──▶  USB cable  ──▶  S1/S2 USB adapter  ──APIB cable──▶  SYS-2522
APWIN (Windows)  ──driver──▶  USB cable  ──▶  (the same adapter)  ──APIB cable──▶  SYS-2522
APWIN, originally ──ISA bus──▶ ISA-WIN card, 8 I/O ports at 0x2B8 ──APIB cable──▶ System Two

Opening the adapter

What the app does at connect, verbatim from APDevice.swift (and the same as APWIN in the captures):

libusb_open_device_with_vid_pid(0x16D0, 0x0AF0)
set_configuration(1) if not already; claim_interface(0)
control IN  bmRequestType 0xC0, bRequest 0x54, wValue 0,      wIndex 0, length 2  → replies 00 0C
control OUT bmRequestType 0x40, bRequest 0x53, wValue 0xFFFF, wIndex 0, length 0
control OUT bmRequestType 0x40, bRequest 0x55, wValue 0x0000, wIndex 0, length 0

After those three vendor requests, everything is bulk transfers: commands out on endpoint 0x06, replies in on endpoint 0x82. The code's comments call the adapter's chip an "FX2" (a common Cypress USB microcontroller); that has not been checked against the device's own descriptors.

One command, one packet

Each command is one bulk OUT transfer. If the command produces a reply, the reply is the next bulk IN transfer, before the next command is sent; many commands produce none. (Getting this pairing rule wrong once produced about 4,000 false differences in a replay-versus-capture comparison: docs/usb_pairs.py.)

The first byte is the opcode. These are all the opcodes in T92, with how many times each appears:

opcodelength outreplycount in T92what it doeshow sure
8A4 or 5none16,356write a register: 8A <width> <reg> <data…>known
EB31 or 255,482read a register: EB <width> <reg>known
E64217,440write/arm a DSP port: E6 <dsp> 0A <value> → 01 <echo>known
E9359,531read a DSP value with status: E9 <dsp> 0A → 01 <status> <24-bit value>known
ED759,306DSP read at an addresspartly
EC1111,239write one DSP parameter: EC <dsp> 0A 00 00 <addr> 2A <3 data bytes> <flag> → 01 (accepted) or 00 (refused, DSP busy)known
E862460DSP transaction, used while loading coefficientspartly
0B62378a combined write-then-read (0B 01 37 EB 01 37)partly
0818–1925 or 64207DSP program block upload (the 192-byte ones reply with 64 bytes)known
EF662487DSP calibration data blockpartly
03up to 3,098none26large data blockunknown content
835–26none49data block, variantunknown
822none115select a slot or module (82 <slot>)partly
024139read a module's identity (slot enumeration, EEPROM)known
01313identify the deviceknown

Two observations about the structure, both inferences rather than documentation:

The register space: 8A and EB

For six months the app's code named the second byte of 8A and EB a "module" (0x01 system, 0x02 "sys2"). It is a width. In all 72,000 of these commands in T92, 8A 01 … is always 4 bytes long (one data byte) and 8A 02 … always 5 (two data bytes); EB 01 xx always returns one byte and EB 02 xx always two. The third byte is the register address, in one flat space. (Found while writing this page, 2026-10-21; the code still uses the old names.)

Examples, all verified on the instrument:

8A 02 21 7D A8      generator frequency word: 7000 Hz (two bytes, little-endian)
8A 01 20 81         generator mode: analog IMD
8A 01 2B 48         generator output: balanced, floating, 40 Ω
EB 02 1C  → 70 44   channel A level mantissa: 0x4470 = 17520 (T92, during SMPTE)
EB 01 0D  → 0D      the instrument's handshake counter

Two-byte values are little-endian. An early note read the second byte of a level reply (7C) as a "valid" flag; it is the high byte of the number.

The DSP side: E6, E9, ED, EC, 08

The second byte of these is a DSP address: 0x30 and 0x38 are analyzer channels A and B, 0x40 and 0x48 the generator side (the app's naming, from captures). The third byte is almost always 0A, a DSP port. E9 replies carry a status byte (0x86 busy, 0x87 done) ahead of a 24-bit fixed-point value.

The DSP programs travel over this link too: APWIN uploads them, 207 08 blocks in T92's re-initialisation. That is a large part of why every Cascade measurement begins by replaying a recorded setup.

Volume and timing

Measured in T92 from the capture's own timestamps:

quantityvalue
time from a command to its reply, typicalabout 100 µs (median 96 µs for EB, 97 for E6, 125 for E9, 194 for EC, 224 for ED)
a DSP program block (08)about 1,700 µs
gap between one command and the nextmedian 110 µs
commands with a replyabout 85 %
average rate over the whole capture613 commands a second (APWIN spends much of its time waiting)

How many commands real work takes, from the app's own recordings:

taskcommands
FFT setup replay122,703
T92 replay up to SMPTE IMD57,053
THD setup (GenMon, −20 dBV)50,396
connect: cold start + generator config + measurement setup40,065 + 600 + 13,237

And it is a conversation, not a stream. APWIN, and Cascade, poll until the instrument says ready before the next write: a DSP write sent while the DSP is busy is refused (EC replies 00), and in October that left a replay broken until the next one. The instrument also keeps its state across USB disconnects (a hung run in October needed a power cycle).

An APIB-to-Ethernet interface

The comparison Chris drew, Prologix, works for GPIB because GPIB is the easy case: a published standard (IEEE-488), and an instrument that understands text. A box can take a line of text from the network and put it on the bus, and the round trip hardly matters, because one command does a lot of work.

APIB is the hard case: tens of thousands of small register transactions per measurement, most of them waiting on a reply. So the question is not whether the bytes can travel over Ethernet (they can) but where the waiting happens. Three ways to build it:

1. Tunnel the USB. Put the existing adapter on a small computer next to the instrument and forward the USB traffic over the network (Linux has a standard way to do this, USB/IP). Cascade would not change. But every reply-bearing command pays a network round trip instead of 100 µs. On a wired network a round trip is commonly a few hundred µs; on Wi-Fi often several ms (typical figures, not measured here). For the 122,703-command FFT setup that is roughly the difference between tens of seconds and many minutes. It works; it is slow.

2. Network the commands, not the USB (recommended to try first). The same small computer and adapter, but running a small server that speaks this opcode protocol. Cascade sends commands over TCP instead of libusb; the server sends them to the adapter and returns the replies. The win comes from moving the waiting to the instrument's side:

Cascade's code already funnels every transaction through a few functions, so swapping the transport is a contained change. Nothing about the instrument has to be learned that we don't already know.

3. A true APIB-Ethernet adapter, like a Prologix box: a microcontroller with Ethernet on one side and the APIB connector on the other, replacing the USB adapter entirely. This needs what we don't have: APIB's electrical layer and bus timing. Getting it means putting a logic analyzer on the APIB cable between today's adapter and the instrument while a known command sequence runs, and matching bus activity to the USB commands above. It is the most work, and the most interesting. It could also absorb approach 2's server into the box.

Approach 2 gives a networked Cascade with the hardware already on the bench and teaches us which operations need to live next to the instrument, which is the design input approach 3 would need anyway.

What we still don't know