In Java-based applications that use it, librxtx introduces an additional 20ms of latency. I wasn’t using Java or librxtx, but you’ll want to read that if you are.

The good news is that you can reduce FTDI latency substantially with a simple tweak.

What’s Latency?

In this case, latency is the amount of time between when some data gets sent from one side (the Arduino), and received on the other side (the computer.)

In lots of cases latency doesn’t matter, or you accept higher latency in exchange for higher throughput. However, for real-time applications like MIDI controllers, you don’t want a noticeable delay between pressing a button and hearing the sound that it makes.

The consensus seems to be that for acceptable MIDI audio responses, you need to keep MIDI message latency under about 20ms.

FTDI Latency Timer

The problem stems from the Arduino’s “Serial to USB converter” chip, the FTDI FT232R. The FTDI can’t send a USB packet to the computer for every byte that comes from the Arduino’s microcontroller. Instead, it stores the serial data in an internal buffer and only sends a USB packet when the buffer is full, or after a period of time has elapsed. This period of time is determined by the FTDI Latency Timer, which is the reason why FTDI chips can give bad latency characteristics.

On Linux & Windows, the default latency timer setting is 16ms. For example, say you send a 3 byte MIDI message from your Arduino at 115200bps. As serial data, it takes 0.3ms for the MIDI message to go from the Arduino’s microcontroller to the FTDI chip. However, the FTDI holds the message in its buffer for a further 15.8ms (16ms after the first byte arrived), before the latency timer expires and it sends a USB packet to the computer.

Thankfully, the latency timer can be tweaked. The tweaking method varies between operating systems.

Linux

In proper Linux style, the kernel’s FTDI driver exposes a nice sysfs interface that lets you get and set the latency timer. For example, if your serial port is ttyUSB0:

… that will lower the timer from 16ms to 1ms (the minimum), to reduce latency.

In my experience, the timer value won’t change immediately on an open serial port. If an application is using it then you’ll need to close and reopen it before the new value takes effect.

If you’re writing code, there is also a Linux-specific serial flag ASYNC_LOW_LATENCY that programmatically sets the latency timer down to 1ms. This is how Hairless MIDISerial Bridge does it. You can see a succinct C code example in this patch I submitted to the ttyMIDI project.

In testing, I found that ASYNC_LOW_LATENCY also only works if you subsequently close the serial port and then reopen it (annoying, because setting the flag requires you have open()ed it already.)

Windows

FTDI’s own driver for Windows has a combo box in the Port Settings dialog that lets you choose the latency timer value. This Instructable has some screen shots showing how to find the setting in the Windows Device Manager control panel.

Programmatically, setting the timer is a bit hackier on Windows but not impossible. The FTDI driver saves the current latency setting for each device in the registry, so you can use Microsoft’s Registry API to write a new value, then reopen the serial port. The registry key is

OS X

OS X does things differently. The driver bundle contains a file, /System/Library/Extensions/FTDIUSBSerialDriver.kext/Contents/Info.plist. This XML plist file describes different profiles for the serial port, including different LatencyTimer values, depending on how the FTDI identifies itself on the USB bus. FTDI’s own Technical Note on the subject [PDF] explains how to edit that value to change the latency.

The good news is that on OS X the latency timer defaults to 2ms for any FTDI FT232 that uses the default vendor & device USB IDs (0403:6001). This includes Arduino & clone FTDIs, so there is no real need to change anything.

How Fast?

I ran a test of the tweaked latency timers. The test sketch sends a MIDI note (3 bytes), then waits to see that same note echoed back. It does this many times and calculates the average delay.

With the latency timer set to 1-2ms, the entire round trip averages 18-19ms. This includes at least 1ms spent in the MIDI framework on the computer. So, based on those results, I estimate the one-way latency to be under 10ms. Excellent!

With a 16ms latency timer, the one-way latency would have been 25ms or more.

I don’t know how much of the 10ms latency is now coming from FTDI/USB layer, or from higher layers in the host operating system. It’s good enough for MIDI use, so I stopped investigating!

Which Arduinos?

Arduinos with FTDI chips include the Arduino Duemilanove & Mega, and some clones like the Seeeduino.

The newer Arduino Uno & Mega 2560 have a different AtMegaU8 chip, programmed to behave as a USB/Serial converter. According to tests I’ve seen, these have good latency characteristics.

5 thoughts on “Notes on FTDI latency with Arduino”

Thanks for the complete solution for different OS!
I have been using cat / echo /sys/bus/usb-serial/devices/ttyUSB0/latency_timer method for a while to get / set latency_timer for FTDI USB-serial port.

However, on a Ubuntu 16.04.2 LTS computer, we got following error:
cat: /sys/bus/usb-serial/devices/ttyUSB0/latency_timer: No such file or directory

That’s odd! My laptop has kernel 4.10.13 and I can confirm this sysfs entry is still there for that kernel version. Are you sure the device is an FTDI and not something with a different USB to serial chip?

Yes, the VCP is FTDI USB-to-serial port (VID=0x0403, PID=0x6001). It works well for communication both way, but we just failed to query and change its latency_timer. Strangely, this does not always happen for the 16.04.2 LTS computers, but only one of them.

Anyway, we have a better solution for latency_timer, which is used by Psychtoolbox. The solution is to create a .rules file under /etc/udev/rules.d/ with the following content:
ACTION==”add”, SUBSYSTEM==”usb-serial”, DRIVER==”ftdi_sio”, ATTR{latency_timer}=”1″

This will reliably change latency_timer to 1 ms. You may include this solution to your post.