Communications Device Class (CDC) driver

The Communications Device Class (CDC) driver uses the ACM subclass. Only a limited set of control functions are implemented beyond providing a stub. If you need them, expand them to suit your requirements.

There are two USB interfaces, a control interface with an interrupt in endpoint, and a data interface with a bulk in and and bulk out endpoint per COM channel.

The COM channel(s) are implemented as the ends of CQUEUE structures. These queues should be large enough to hold the number of bytes accumulated during a USB poll interval. The poll interval is defined in the endpoint descriptor for the control interface. By default, this is set to 1, indicating a poll interval of 1 ms.

Data and buffers

-1 value RunCDC?        \ -- x
Set non-zero when CDC operation is enabled.

#10 aligned buffer: NotifyBuf   \ -- addr
Holds CDC notification data.

7 aligned buffer: LineCodingBuf \ -- addr
Holds current line coding information.

2 buffer: SerialState           \ -- addr
Holds serial state.

2 buffer: LineControlState      \ -- addr
Holds Line Control State info.

create InitNotifyBuf    \ -- addr
Initialisation data for the notify buffer

create InitLineCoding
Initialisation data for the line coding, usbcdc11.pdf 6.2.13 p57.

CDC Class RTI requests

: CDCdoEP0ClassRTI      \ --
Handle the CDC class REQUEST_TO_INTERFACE operations.

CDC Bulk endpoint handling

variable #LastIn0       \ -- addr
Holds the number of bytes in the last packet sent to the host. This is used to prevent a Windows problem that occurs when the last IN packet contains /MaxPacket bytes. In this case a following zero-length packet is required.

Non DMA operation

Operation without DMA drivers is portable across a wide range of CPUs and USB engines.

/MaxPacket buffer: CdcBulkBuf   \ -- addr
Packet buffer for CDC bulk transfers.

0 value CDCdeferred?    \ -- flag
Set true if the serial input buffer is full. The current packet is then retried in the frame interrupt.

: CdcBulkOut    \ --
Handle bulk OUT actions to the device. Note that this includes NAK interrupts.

: CdcBulkIn     \ --
Handle bulk IN actions to the host. Note that this may include NAK interrupts.

: doCDCbulkInOut        \ event --
The action of endpoints which combine CDC BULK IN and BULK OUT.

: doCDCbulkIn   \ event --
The action of an endpoint for CDC BULK IN only.

: doCDCbulkOut  \ event --
The action of an endpoint for CDC BULK OUT only.

: CdcFrameCheck \ --
Executed in the frame interrupt.

DMA operation

Because of the variety of DMA engines used for DMA, isolation of a hardware dependent DMA layer is more complex. As yet, we do not guarantee that the hardware dependent DMA layer is portable to all CPUs and USB engines.

CDCBoutEP BulkDD:
Bulk OUT DMA descriptor.

CDCBinEP BulkDD:
Bulk IN DMA descriptor.

/MaxPacket buffer: CdcDmaOutBuff        \ -- addr
DMA buffer for data OUT from host.

/MaxPacket buffer: CdcDmaInBuff \ -- addr
DMA buffer for data IN to host.

CDCBoutEP EPaddr equ CDCBoutPhys        \ -- u
Physical address of the CDC Bulk OUT channel.

CDCBinEP EPaddr equ CDCBinPhys  \ -- u
Physical address of the CDC Bulk IN channel.

: initCDCbo     \ --
Start the next CDC Bulk out DMA transfer.

: initCDCbi     \ --
Initialise the CDC Bulk IN DMA transfer.

0 value CDCdeferred?    \ -- flag
Set true if the serial input buffer is full. The current packet is then retried in the frame interrupt.

: CdcBulkDmaOut \ --
Handle CDC bulk DMA OUT actions to the device.

: CdcBulkDmaIn  \ --s
Handle CDC bulk IN actions to the host.

: doCDCbulkInOut        \ event --
The action of endpoints which combine CDC BULK IN and BULK OUT.

: doCDCbulkIn   \ event --
The action of an endpoint for CDC BULK IN only.

: doCDCbulkOut  \ event --
The action of an endpoint for CDC BULK IN only.

: CdcFrameCheck \ --
Executed in the frame interrupt. If there is data to be sent to the host, Bulk NAK IN interrupts and channel DMA are enabled.

CDC Interrupt endpoint

: doCDCintIN            \ event --
The action of the CDC interrupt endpoint IN.

Reset and initialisation

: resetCDCdata  \ --
Reset the CDC driver data, initialising CDC specific data to start/restart operation. Performed at start up and as an EP0 action.

: DevCDCreset   \ --
Reset the CDC driver, initialising the CDC specific data to start/restart operation. Performed as an EP0 action.

: CDCreset      \ -- flag ; 0=failed
Reset the CDC driver. EP0 action.

Diagnostics and Test code

: UsbCon        \ --
Switch the Forth console to the USB channel.

: DefCon        \ --
Switch to the default Forth console, normally on UART0.

: UsbDisCon     \ --
Switch to the default console (usually a serial device) and disconnect the USB from te host.

Testing with operating systems

Windows

USB serial devices can be determined using:
Control Panel -> System
Hardware -> Device Manager -> Ports

Once a USB serial device has been found, it will usually use the same port number when reconnected. To use two or more of the same device, they must have different serial numbers.

For testing we use PuTTY or HyperTerm and AIDE's PowerTerm. HyperTerm is a very old program written before USB serial ports were available. It does not have sufficient error recovery for USB serial ports, and so is not suitable for production use. Despite this, it is available on all Windows PCs and your clients will use it. PuTTY and TeraTerm are widely recommended by USB developers.

Under Windows, a .INF file is required for any USB device that includes a CDC class driver. The PID and VID (see below) must match those in your USB device descriptors, as must the manufacturer string. Sample .INF files are provided, which you can use as the basis of your own. Windows 10, 8, 7, Vista, and XP SP3 work well, but XP SP2 is not so robust. For XP, either upgrade to SP3 or install hotfixes. It seems that you need:

We installed SP3 for testing and had to copy the new version of usbser.sys manually from SP3.cab. Use Windows search and copy the new version to replace the one in Windows\System32\Drivers.

The Windows INF file for many CDC implementations is mpecdcW7.inf. When installing this on Windows 7 onwards, Windows will probably say that it cannot find a driver automatically. To overcome this, select manual installation. Make sure the INF file is the only INF file in the selected driver directory. Windows will then complain that the driver is unsigned. Tell Windows to carry on regardless and the INF file will install.

Linux

USB serial devices

When using USB serial devices, the name used varies according to your distribution. The most common names appear to be:

  /dev/ttyUSBx
  /dev/ttyACMx

There are several methods of finding USB serial ports. The simplest seems to be to unplug the device, then reconnect it, then type the following incantation:

  dmesg | grep tty

where you must have root access. On many systems, e.g. Ubuntu

  sudo dmesg | grep tty

is required. The last few lines should then tell you which USB serial port, e.g. /dev/ttyUSB0 was selected for your device. If the last tells you that the device is now disconnected, it is probably because of the "brltty bug". Unless you need the Braille TTY access, remove the package brltty. Repeat:

  sudo dmesg | grep tty

to check that device remains connected. Some forums suggest that you may also need to create the /dev/ttyUSBx entries. Do this with:

  sudo mknod /dev/ttyUSB0 c 188 0
  sudo mknod /dev/ttyUSB1 c 188 1
  sudo mknod /dev/ttyUSB2 c 188 2

Linux serial terminal emulators

The most widely used Linux equivalent to Windows' HyperTerm appears to be minicom. It isn't pretty, but it works and is easy to use. There are plenty of others, including GUI ones, but minicom is the one we come back to as it is available for nearly all distributions.

Mac OS X

For automatic recognition by Mac OS X, bit 0 of the protocol byte in the relevant descriptors must be set. After enumeration, your USB serial sevices will appear in the forms:

  /dev/tty.usbmodem000031FD1
  /dev/cu.usbmodem000031FD1

Tty is for "incoming calls" and cu for "outgoing calls". It has to do with behaviour when opening the port, though you can achieve any behaviour you want on either device by opening it non-blocking and reconfiguring the tty settings.

To test a serial device, run a terminal

  Applications -> Utilities -> Terminal

Within the terminal run the screen program.

  screen /dev/tty.usbmodem000031FD1

From a terminal you can get the documentation:

  man screen