Serial.write() on USB not doing transmit buffering of multiple writes correctly

A strange time-sensitive bug in my application, running on Teensy++ 2.0 and Teensyduino 1.15, finally distilled down to this:

Code:
    byte buf1[] = { 'a', 'b', 'c' };
    byte buf2[] = { '>', '>', '-', '-', '0', '1', '0', '1' };
    byte buf3[] = { 'x', 'y', 'z' };
    Serial.write(buf1, 3);
//    delay(5);
//    Serial.send_now();
    Serial.write(buf2, 8);
//    delay(5);
//    Serial.send_now();
    Serial.write(buf3, 3);
//    delay(5);
//    Serial.send_now();

If I run this over the serial monitor, it works fine. When I run it for real reading the USB response on an Android device (I've tried several models), I get no response at all.

If I un-comment the first Serial.send_now(), I get the first 3-bytes, then nothing.

If I un-comment all three Serial.send_now(), everything works. Likewise if I instead un-comment all delay(5).

I noticed here that Teensy will flush the transmit buffer after 3 ms, which made me begin to suspect that without this delay, it attempts to collect the multiple writes but somehow botches it.

With other combinations of timing and buffer sizes, I've observed zero-byte packets, incorrect response sizes, and intermittently portions of the data getting zero-filled. I would expect all these are manifestations of one underlying problem.

For now I'm working around the problem with Serial.send_now() everywhere.
 
I would like to investigate this, but I do not own any Andriod devices. Even if I borrowed one, how would I duplicate your setup?
 
Well, I know next to nothing of how USB works under the hood, but my guess is that the bulk transfer should behave the same on any device, Android or otherwise, and the serial monitor is doing something peculiar that circumvents the problem.

So with whatever you have handy to read USB, does it behave the same way?

In case it actually is something specific to Android or to the devices I tried, people say (though I haven't tried) that you can install android-x86 on virtualbox and access the USB. But even if I provide an APK to demonstrate, is that enough that you can investigate?
 
USB is a complex protocol. The USB host (not Teensy) has a tremendous amount of control over the entire process, so it's entirely possible the actual packets on the wire could be quite different between 2 different systems, even if Teensy is running the same code.

Teensy can implement several USB types. You select the protocol from the Tools > USB Type menu. We're talking about "Serial", right?

Zero length packets are a normal part of the communications class (USB serial) protocol. Teensy ignores them when sent by the host, but will transmit them at the appropriate times as the protocol requires.

When Tools > USB Type is set to "Serial", the Arduino Serial Monitor uses the RXTX library to communicate with Teensy. There's no special USB code at all inside Arduino. The Arduino IDE is written in Java, which doesn't even have APIs for USB stuff. The only "special" thing it does is to open the port at a couple different baud rates. Setting the baud rate to 150 is used as a signal to restart your program, so the serial monitor sets the baud rate to 150, then to whatever the menu says. Teensy doesn't use the baud rate at all, since it's USB virtual serial, other than this signal and of course it's available for you to read what number the host configured (see File > Examples > Teensy > USB_Serial > USB2Serial for an example).

Why you're seeing strange things with Andriod, I do not know.

If I had an Android device here with your software (and someone familiar with how to use it), and the Teensy running your code, I'd connect a USB protocol analyzer between them and capture the actual communication. That would show what's actually happening on the USB cable.
 
How do you connect the Teensy to Android, trough a USB cable or Bluetooth ?

What application is used on Android to read the incoming transmissions ?

Is there any line buffering in place ? So messages should be termintated by CR/FL.

Just some little things to check ...

Regards
Magnus
 
I am not planning to buy a driod, nor am I planning to spend time figuring out how to replicate this testing, even if the software and good step-by-step instructions were posted.

Teensy's USB Serial (CDC ACM protocol) implementation has been stable for years and widely used for a very wide range of projects on numerous platforms. I've heard from people before using Teensy together with tablets and phones. Those platforms have not been trouble-free, but problems have turned out to be on the host side. I'm not absolutely denying any chance there's a bug on the Teensy side, but I believe it's extremely unlikely.

However, if anyone will bring this software on an Android device to a DorkbotPDX meetup here in Portland, Oregon, USA, I'll bring my USB protocol analyzer. We can hook it up and run the test (and then have a beer). I'll post the capture file here. Anyone can download the software from Total Phase to view it. I'll put some work into analyzing the data (probably a few days later...).

If the packet log shows the problem is on the Teensy side, I will of course work to fix whatever is wrong. But until I have an actual packet log, I believe it's very unlikely the data loss will turn out to be on the Teensy. This USB Serial code is very mature and has been well tested over the last few years.
 
Last edited:
Thanks for taking time with this, but after writing it up and thinking about what I wrote, it became obvious that the problem, in this case at least, is in Android and not Teensy.

The test is to do a single Serial.write() with a big buffer containing everything together, versus multiple smaller writes. And everything behaves consistently.

My working theory is that, on the Android side, if I try to read less data than the entire packet available, in this case 3 bytes out of the 14, it fails. Unfortunately there is no straightforward way in my Java code to determine how it failed, whether from simply running out of data to read or some other cause. If I give it a big enough buffer to read into, say a thousand bytes, then everything works, regardless of how I send from Teensy. So that's another workaround, although it's not obvious how to know how big a buffer I need for each read in general.

And yes, Teensy's handling of USB inspires much more confidence than Android's. :)
 
I have a related problem with Serial.print and Serial.write. I have two Bluetooth modems connected on Serial 1 and Serial 3. If I write a series of single byte writes as in - Serial1.write(0x31) and Serial3.write(0x34) - I get the correct characters in the correct places. However, when I use - Serial1.print("1") and Serial3.print("4") - both characters get sent to the Serial1 port.

I have tried multi-character strings and I get the same result. The fact that Serial.write ( to ports 0,1,3) works as expected appears to rule-out the hardware, so there may be an issue with Serial.print. I should also add that Serial.print works fine on USB/UART0, the issues appears to be ports 1,2, and 3.

I don't buy the Android buffering theory because when I send strings to the Android BT terminal they appear correctly for Serial1 but nothing appears on Serial3. My analysis so far points to the Serial.print routine for UART 3, and perhaps UART2 but I've not tested this.

Any comments most welcome and apologies in advance if I've broken any forum etiquette.
 
Last edited:
If I write a series of single byte writes as in - Serial1.write(0x31) and Serial3.write(0x34) - I get the correct characters in the correct places. However, when I use - Serial1.print("1") and Serial3.print("4") - both characters get sent to the Serial1 port.

Install Teensyduino 1.15.

This was a known bug in 1.14, caused by a mistake in optimizations added to Serial1. It's fixed in 1.15.
 
Install Teensyduino 1.15.

This was a known bug in 1.14, caused by a mistake in optimizations added to Serial1. It's fixed in 1.15.

I am curious is there an easy way to identify which version of Teensyduino is installed? I am currently running with 1.15 (I checked be kicking off the installer again) but I was wondering if there is an easier way to identify the installed version?

Cheers Kb
 
Back
Top