A Overview of USB Programming in Linux

User Space and Kernel Space

The Linux kernel and its device drivers, operate in a region of memory separated from that of user programs. This separation is enforced by the microprocessor. When data and instructions must pass between the two, the communication is carefully controlled to protect the system from errant programs.

The mechanism used is the reading and writing from and to special filesystems hung on the normal disk file tree. These might be called "fake" or "pseudo-" filesystems, because they look like disk files, but are actually windows into the kernel. If you look at their size, you will typically find 0 bytes, but if you do a cat on them, you will get data back. That data is generated on the fly by the kernel and represents the absolutely up-to-the-microsecond kernel values. Some files let you 'echo' values into them, and those values immediately change what the kernel uses when it needs them.

The three pseudo-filesystems of interest here are:

When programming Linux, there is a very important concept to remember:

"All devices are handled like ordinary disk files, they can be opened, read, and perhaps written to as dictated by their capabilities and associated permissions."

Each device driver is connected with at least one entry in the /dev directory, and it will handle a small number of functions that act on that entry. Some common functions are:

The specific case of the FTDI-2232C chip in a DLP-2232PB-G device on my computer

My computer has 6 USB ports that can connect to any of 3 USB 2.0 buses. I currently have built into my Linux kernel support for an EHCI hi-speed controller and an OHCI "full" speed controller.

When the DLP-2232PB-G device (see previous page for detailed references.) is plugged into a USB bus port on a computer running Linux, core USB software does the following:

The entries under the /dev/bus/usb directory are actually all is required to gain full access to any USB device. The programming, however, can be quite tedious, and additional levels of code have been created to make it easier. Unfortunately, these additional levels nearly always reduce coding flexibility. They are:

It is clearly arguable how much capability should be put into the kernel, whether a specialized kernel driver is needed for a particular device, and whether general application libraries are justified. If application libraries are justified, how should they be organized?

Currently in Linux, the kernel USB code implements the USB 2.0 standard and related upkeep of /sys, /dev, and /proc. More specialized USB drivers should serve a common class of applications, such as interfacing traditional serial programming techniques with the USB system. The ftdi_sio driver serves a wide range of devices that use the FTDI chips as serial converters between USB and the legacy serial protocols.

Programming serial I/O through a FTDI-2232C Chip

The DLP-2232PB-G has both channels of its FT-2232C chip connected to a PIC16F877A microcontroller chip. Channel A is used for programming the PIC and channel B is used to send commands and get results. The FT-2232C EEPROM is set to make the FT-2232C power up with both channels in FIFO mode. This allows serial communication to pass through channel B of the FT-2232C into in the PIC16F877A. The default DLP firmware in the PIC16F877A takes the commands and provides responses via this serial connection:

application program <=> ftdi_sio <=> USB Bus <=> FT-2232C Channel B <=> PIC16F877A

This is illustrated in a simple application program that uses ftdi_sio to communicate with the DLP-2232PB-G protoboard.

This code necessarily uses asynchronous communication with special kernel messages called "signals" announcing the arrival of newly received data. Normally, serial communication is performed line-by-line with newline (0x10) characters marking the end of a line. The tty driver to which the ftdi_sio driver connects normally waits for the newline characters before passing the received message to the application program. If there are no newline characters, the application program gets hung on its read instruction.

Signals solve this problem, but signals need to know where to go.

From the normal documentation, it appears that asynchronous communication may be specified when opening the device by using the O_ASYNC parameter. This is not the case, however, since the tty driver does not yet know where to send the signals. Only after the fcntl command is used to explicitly set the owner of the newly opened file descriptor to the process id of the application program, can O_ASYNC be set. (See the simple application program for details.)

After requesting PIC16F877A to identify itself, the above program makes sure that the communication from application program to PIC16F877A correctly passes all possible 8-bits bytes. An incorrect setting of the termios parameters can lead to various control bytes being changed, dropped, or added.

An Oscilloscope Using the DLP-2232PB-G

A program for using the computer and DLP-2232PB-G as a low-frequency oscilloscope is a rather straight-forward extension of the simple example given above. The only difficult part is the addition of X-windows code to display the resulting graph of voltage vs time. Its voltage range is 0.0 - 5.0 Volts with a resolution of 1 mV. Using the default DLP code in the 16F877A, the time resolution of this oscilloscope is 2 ms.

If you just want to monitor logic levels vs. time, you can display all 8 digital inputs of a 16F877A I/O port. You then have an 8-channel logic scope. Here is a program to do that which uses one of the pins as a trigger. Its time resolution is the same as the low-frequency oscilloscope since all 8 channels are communicated in a single byte from the 16F877A.

Modified 16F877A code that sends the readings in blocks can speed up this scope considerably. Here is the improved 8-channel logic scope output. The time resolution has improved to 4.0 microseconds/reading. The general-purpose oscilloscope using the unit's A/D converter ends up with 24 microseconds resolution when the modified code is used.

Changing modes and other FTDI settings using the ioctl I/O Control Command

ftdi_sio, like most drivers, can handle an ioctl command. ioctl's are a way of submitting oddball commands to the device that cannot be handled using the open, read, and write commands. Typically, these have to do with communication mode changes.

The FT-2232C chip has a variety of communication modes and can have its EEPROM reprogrammed over the USB link. Accessing these capabilities involves sending control messages via the pathway that would normally be used only by the USB host controller. This is what the ioctl commands can do, but unfortunately, the ftdi_sio driver only allows a few ioctl commands related to changing the serial protocol. It does not allow general ioctl commands to be passed to the FTDI device. We could enhance the ftdi_sio driver, but we don't need to.

The solution is to disconnect and reconnect the ftdi_sio driver as needed from within the program. We must do this in order to read and write program code and data in the 16F877A of the DLP-2232PB-G unit. Whereas the command/response communication between the FTDI chip and the PIC chip is via channel B acting as a FIFO, the programing lines of the PIC chip are controlled by channel A in a special serial mode (MPSSE, see below) that must be set via an ioctl message. It is necessary to first contact the chip directly using the libftdi and libusb functions to set the communication mode of channel A of the FTDI chip.

When the DLP-2232PB-G is plugged in, the ftdi_sio driver is automatically connected with it. The programmer code first disconnects the ftdi_sio driver from channel A of the FT-2232C and then connects directly to the /dev/bus/usb/<device number> file. It uses libftdi functions to set its communications mode to MPSSE (Multi-Protocol Synchronous Serial Engine), a mode very nicely suited to programming the PIC chip.

Finally, a custom-written function, not available in either of those libraries, directly sends an ioctl call to the default device kernel driver handling /dev/bus/usb/<device number> entries (drivers/usb/core/devio.c) to tell it to again match up USB kernel drivers with the connected devices they can drive. Here is that function:

void reconnect_kernel_drivers(){
  int fd;
  int ret;
  int interface;
  unsigned int val1, val2;
  struct usb_ioctl {
    int ifno; /* interface 0..N ; negative numbers reserved */
    int ioctl_code; /* MUST encode size + direction of data so the
                     * macros in <asm/ioctl.h> give correct values */
    void *data; /* param buffer (in, or out) */
  };
  struct usb_ioctl command;

  val1 = 3 << 30 | 'U' << 8 | 18 | sizeof(command) << 16;
  val2 = 0 << 30 | 'U' << 8 | 23 | 0 << 16;
  printf("val1=%04X  val2=%04X \n", val1, val2);
  interface = 0;
  command.ifno = interface;
  command.ioctl_code = val2;
  command.data = NULL;

  fd = open("/dev/bus/usb/001/001", O_RDWR);
  printf("%04X\n",fd);

  ret = ioctl(fd, val1, &command);
  if (ret){
    err(1, NULL);
    printf("could not attach kernel driver to interface %d: %d\n", interface, ret);
  }
  close(fd);
}

Once the ftdi_sio driver has re-attached itself to our device, we will need to know which /dev/ttyUSB<port number> should be used. There is a routine called usb_device_nbr() in the load_16f877a.c code that uses the /sys directory to match the correct port number with the identification strings for the particular device, including a description string and a serial number string.

After a wait of about 50 mS, the connection to /dev/ttyUSB<port number> can be made, MPSSE programming initiated, and PIC16F877A program code read and written.

The programmer code works, but is not as efficient as it should be. For some reason, I found it necessary to leave and return to programming mode after each write, and to reset the program counter to the appropriate value for the next write. This leads to increasingly inefficient programming as the address of the code increases. Fortunately, the speed of programming is sufficiently high that complete programming is still possible in about 12 seconds. This inefficiency should be resolved before too long.

This same technique for mixing libftdi, libusb, ftdi_sio driver, and default driver code also allows seamless programming of the EEPROM connected to the FT-2232C chip.

Throughput and Timing Considerations

When using the DLP-2232PB-G or when changing its embedded code, the actual commands and data pass through at least three processors and at least 3 communication protocols with associated buffers and clock rates:


Last updated: September 13, 2007

Valid CSS! Valid XHTML 1.0 Strict

Contact Craig Van Degrift if you have problems or questions with this web site.