-
Notifications
You must be signed in to change notification settings - Fork 1.6k
This is a collection of FAQ-style troubleshooting tips.
No OTG adapter is required if the serial device correctly implements USB-C. Typically the the CC pins are connected to GND with 5,1 kΞ© resistors each, to mark the device as USB peripheral.
Connecting an USB device with just an USB cable will not work. You have to attach an OTG adapter to your Android device to switch it into host mode. This adapter typically provides an USB A socket, where you attach a standard USB cable.
Check OTG adapter as discussed in previous question.
Check with an App like Serial Example in this project, if your device is shown. If no USB device is shown, check the web for USB Host Mode aka. USB OTG support for your Android device. Missing support is more likely for older devices.
For an incomplete list of older Android devices with known issues look here.
The library includes a list of USB vendor and device ID's (VID/PID) that are typically used by the supported chips. In case of unusual ID's, use a Custom Prober or specify a fixed driver.
Non USB-to-Serial devices like mass-storage, mouse, GPC receivers, ... and rarely used USB-to-Serial chips including Hoitek HE2325U and FTDI FT312D are not supported.
A common issue is wrong baud rate.
Typically an issues with the USB device, e.g.
- USB requests are temporarily not handled in your program
- power supply insufficient, causing hardware reset
Not all drivers / devices support non-standard baud rates. See Feature Matrix for details.
I am using an Arduino Uno, Sparkfun Pro Micro, or other Arduino but Serial.write()
does not send data
Most Arduinos with CDC-ACM driver use the DTR line to determine serial channel readiness. In your Android code, call setDTR(true);
Typically communication and power supply cannot be combined. Might be possible with USB-C and USB PD implemented in the serial device, but typical USB-C serial serial devices only use simple 5,1 kΞ© resistors to GND.
You don't. /dev/tty*
is the Linux kernel's driver interface to various serial devices. Certain devices and custom ROMs may expose serial devices this way, but this library does not use this interface.
After adding vendor-id
and device-id
to device_filter.xml
the USB device connection dialog has a "Always open {app} when {device} is attached" checkbox and the permission dialog has a "Use by default for this USB device" checkbox.
When the checkbox is selected, the permission is retained over USB device re-attach, also after Android reboot.
If the device remains attached during Android reboot, the permission is only retained in some cases:
The issue is identified in Bug 77658221, but set to Won't Fix by Google.
The persisted permission file is only loaded when attaching an USB device, so starting the App after reboot without re-attaching any device, the App again asks for permission.
The bug recommends to add android:directBootAware="true"
to your activity in AndroidManifest.xml
, which improves the situation for some Android devices, e.g.:
USB device attached after boot | USB device attached during reboot | |||
---|---|---|---|---|
Permission retained on App start |
Permission retained on App start |
App autostart | directBootAware | Device |
π’ | π’ | π’ | - | Android 11 / Samsung |
π’ | π’ | π’ | true | Android 8 / Samsung |
π’ | π’ | π΄ | true | Android 11 / Lineage OS |
π’ | π΄ | π΄ | false |
Android 11 / Lineage OS Android 8 / Samsung ... (*) |
(*) Some android devices do not show USB devices that were connected during reboot, so you have to reconnect them anyway.
You can connect to multiple devices and to multiple ports of a multi-port device in one App.
- When connecting to different ports in a multi-port device, each connection needs it's own
UsbDeviceConnection
, else the firstclose
will also close all other ports. - Distinguishing identical devices has some challenges:
- Use devices with different VIDs/PIDs or devices where it can be programmed like FTDI or CP210x .
- Use
UsbDevice.getSerialNumber()
and devices that come with a unique serial or devices where it can be programmed like FTDI or CP210x.
Note: Starting with Android 10, the serial number is only accessible after device access permission has been granted. - Using
UsbDevice.getDeviceName()
orUsbDevice.getDeviceId()
is not an option as the numbers increase when re-attaching a device.
The protocol layer does not guarantee that all bytes will arrive in a single message. In fact the protocol layer doesn't have any knowledge of what your "message" is β it is just an interface for sending a serial stream of bytes.
To reduce the number of USB requests, devices usually buffer data until some size or idle time is reached. Therefore request sizes heavily depend on the device type and actual data transfer pattern.
For example, to receive a 100 byte string, you might read 64 bytes, then 36 bytes, instead of a single message of 100 bytes. You need to account for this problem when designing your protocol. Some common techniques:
- Fixed length messages: If you a message is always going to be 100 bytes, just keep reading until you have all 100.
- Length-prefixed messages: Prefix every message with a fixed-length
size
value; your message is complete after you've readsize
more bytes. - Newline-terminated messages: Read until you see a
\n
(or any other "terminal" character).
Android is not a real time OS. The thread responsible for receiving data might not be scheduled by the OS or garbage collection might be ongoing or β¦ Therefore data loss can happen for continues read at high baud rates. If data is lost, typically some smaller fragments in the middle of the data are missing.
This effect is more likely for slower Android device and USB devices with smaller buffer size and typically starts with higher baud rates like 115k2 baud, but is hardly predictable. For low baud rates or non-continuous transfers this issue was not observed as the USB device will not run into a read buffer overflow before the next USB read transfer.
Using a higher thread priority (default for SerialInputOutputManager
since usb-serial-for-android v3.1.0) and offloading work from onNewData
to other threads can mitigates the issue.
It is recommended to use some sort of flow control like X-Modem protocol.
Data loss can also occur with inappropriate buffer size, as shown in next Question.
USB data transfers have a fixed packet size which is 64 for most devices. You can get the respective size with port.getReadEndpoint().getMaxPacketSize()
, and it's recommended to use it as buffer size.
A smaller buffer size is ok, as long as the received messages fits into the buffer. If it doesn't fit into the buffer, read
returns an empty result. Due to poor error propagation in Android USB code, this cannot be distinguished from timeout or real communication errors.
A larger buffer size can make your app lagging. In particular FTDI devices tend to queue data until the buffer is completely filled on continuous transfer at higher baud rates. This can be mitigated by choosing appropriate buffer size and setting the latency timer as also recommended by FTDI.
When using smaller buffer sizes with FTDI devices, you have to take into account that each response starts with 2 header bytes. The header bytes will be stripped from the final result.
read
timeout should not be shorter then 200 to 500 milliseconds, if possible use infinite timeout.
-
read
with infinite timeout blocks until data is available, so it should be avoided in the UI thread, instead useSerialInputOutputManager
to be notified on new data. -
read
with timeout is typically used in the UI thread, if you send a request and know that there is a timely response and the blocking time is acceptable.
write
timeout should typically be significantly larger than the required send time.
-
write
with short timeout is ok, if you useport.getReadEndpoint().getMaxPacketSize()
as buffer size and resend the remaining data after getting aSerialTimeoutException
. With larger buffer size, the packeting is done in the Linux kernel which returns -1 instead of the written size, so there is no chance to resume writing.
It's typically not.
Write consists of 2 phases:
- from android to usb device
- from usb device to serial port
Waiting for write completion is typically done with application specific protocols, like request-response pattern.
When getCTS()
or getDSR()
return false
you can continue writing until the send buffer is full, then write()
will return a SerialTimeoutException
, but typically it's better to immediately stop writing.
If your App is not reading data fast enough, the RTS or DTR lines are reset when the receive buffer is over 50~80% full.
Some devices return XON/XOFF state with getXON()
method, others return XON/XOFF characters inline in received data. The supported variant is reflected by different FlowControl
modes. The inline data can be filtered out with XonXoffFilter
helper class, which provides a getXON()
method similar to the UsbSerialPort
classes.
When the appropriate getXON()
method returns false
you can continue writing until the send buffer is full, then write()
will return a SerialTimeoutException
, but typically it's better to immediately stop writing.
If your App is not reading data fast enough, CHAR_XOFF
is send when the receive buffer gets 50~80% full, and CHAR_XON
when below that threshold.
Some devices do not have hardware support for flow control. You can check the control line or XON/XOFF characters yourself and can stop writing, but this could still cause data loss if not done sufficiently early.
If your framework can import Java .jar files, look here for instructions about creating a .jar file.
Basic Features are supported by all drivers / devices
Method / Feature | Supported |
---|---|
open , close
|
π’ |
setParameters
|
π’ |
read , write
|
π’ |
event based read with SerialInputOutputManager
|
π’ |
Some Features are not supported by all drivers / devices
Feature | FTDI | CH34x | CP210x | PL2303 | CDC-ACM |
---|---|---|---|---|---|
setParameters with non-standard baud rates
|
π’ | π’ |
π’ for CP2105 π΄ for CP2102. fallback to unknown baud rate without exception |
π’ | π’ |
set BREAK condition | π’ | π’ |
π’ for CP2102, CP2105 first port π΄ for CP2105 second port. resends last byte without exception |
π’ | π’ |
set/get flow control lines RTS, CTS, DTR, DSR, CD, RI | π’ | π’ | π’ | π’ | π‘ only output lines RTS, DTR |
flow control with control lines | π’ RTS/CTS, DTR/DSR | n.a. | π’ RTS/CTS, DTR/DSR | π’ RTS/CTS | n.a. |
flow control with XON/XOFF characters | π’ | n.a. | π’ | π’ | n.a. |
purgeHwBuffers
|
π’ | n.a. | π’ | π’ | n.a. |
multi port devices | π’ | n.a. | π’ | n.a. | π’ |
device specific methods |
get/setLatencyTimer
|
- | - | - | - |