Parallax Propeller2 USB driver - the MashUp

Getting started: serial (instruments, USB-serial cables, CDC gadgets)
Login

Getting started: serial (instruments, USB-serial cables, CDC gadgets)

Status: validated live — FTDI (incl. 4-channel H-series), PL2303, CP210x bridges and CDC-ACM gadgets, plus a real instrument (Alicat MFC) on the expect layer.

What you need

pnut-ts -d -I <path-to>/src -D USB_CDC my_serial.spin2

The smallest program

#IFDEF USB_CDC
#PRAGMA EXPORTDEF USB_CDC
#ENDIF

CON
  _clkfreq = 200_000_000

OBJ
  usb : "usb_app"

VAR
  byte  rx[64]

PUB main() | n
  usb.begin(16)
  if usb.await_serial(5000)
    debug("serial bound, kind=", udec_(usb.serial_kind()))  ' 1 CDC, 2 FTDI,
  usb.ser_open(38400)                                       ' 3 CP210x, 4 PL2303

  usb.ser_write(@hello, 7)
  repeat
    n := usb.ser_read(@rx, 63)              ' one poll; 0 = nothing waiting
    if n
      rx[n] := 0
      debug("rx: ", zstr_(@rx))
    usb.poll()
    waitms(2)

DAT
hello  BYTE  "hello", 13, 0

What bring-up actually does

  1. usb.begin(16) enumerates and binds serial with a deliberate policy: the first vendor bridge (FTDI/CP210x/PL2303) wins over the first CDC gadget — instrument cables are bridge silicon, and a debug console on a composite board would otherwise shadow them.
  2. ser_open(baud) runs the right bring-up for the detected silicon — FTDI divisors and latency timer, PL2303's vendor init dance, CP210x IFC_ENABLE, or CDC SET_LINE_CODING — then 8N1, DTR/RTS asserted.
  3. ser_read is a single poll: data waiting in the adapter's FIFO comes back (max ~62 bytes per call), an empty line returns 0 immediately. Backpressure is inherent — data you haven't read waits in the device.

Talking to instruments: the expect layer

For request/response devices (SCPI gear, AT modems, flow controllers), skip hand-rolled parsing — usb_ser_expect gives tcl-expect-style conversations with globs, timeouts, tokenizing, and evidence-driven autobaud:

OBJ
  usb : "usb_app"
  exp : "usb_ser_expect"

  exp.bind(@wr, @rd)                        ' two one-line shims (below)
  exp.bind_baud(@sb)
  if exp.autobaud(string("A"), string("A*"), 0, 0)
    if exp.chat(string("A"), string("A*"), 0)
      flow := exp.token_milli(2)            ' "+2.503" -> 2503, no floats

PRI wr(b, n) : r
  r := usb.ser_write(b, n)
PRI rd(b, m) : r
  r := usb.ser_read(b, m)
PRI sb(b) : r
  r := usb.ser_set_baud(b)

Several instruments at once

Need more than one link live? Open each by address with its own handle (awaiting hardware validation, ticket 2160):

  h1 := usb.ser_open_dev(addr_mfc, 38400)   ' find addr via dev_*() roster +
  h2 := usb.ser_open_dev(addr_temp, 9600)   ' dev_serial() identity
  usb.ser_write_h(h1, @cmd, n)
  n := usb.ser_read_h(h2, @buf, 63)

Up to 4 handles; per-handle baud/close; handles drop automatically on detach. One cog per handle at a time — but different handles may be driven from different cogs concurrently, which is the point.

Troubleshooting

Symptom Likely cause
await_serial times out Cable on the other bus / not attached — check the dev_*() roster; PL2303 "HXN" variants degrade (detected + reported).
Garbage after replug Device rebooted to its default rate — rerun autobaud() / ser_open().
Instrument answers intermittently Use exp.chat() (it drains stale input first) instead of raw write/read.

Next