diff --git a/usbSerialExamples/src/main/res/xml/device_filter.xml b/usbSerialExamples/src/main/res/xml/device_filter.xml
index ce0fc43..cd9f4af 100644
--- a/usbSerialExamples/src/main/res/xml/device_filter.xml
+++ b/usbSerialExamples/src/main/res/xml/device_filter.xml
@@ -17,4 +17,7 @@
+
+
+
diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java
new file mode 100644
index 0000000..1155512
--- /dev/null
+++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java
@@ -0,0 +1,363 @@
+/* Copyright 2014 Andreas Butti
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ * Project home page: https://github.com/mik3y/usb-serial-for-android
+ */
+
+package com.hoho.android.usbserial.driver;
+
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Driver for CH340, maybe also working with CH341, but not tested
+ * See http://wch-ic.com/product/usb/ch340.asp
+ *
+ * @author Andreas Butti
+ */
+public class Ch34xSerialDriver implements UsbSerialDriver {
+
+ private static final String TAG = Ch34xSerialDriver.class.getSimpleName();
+
+ private final UsbDevice mDevice;
+ private final UsbSerialPort mPort;
+
+ public Ch34xSerialDriver(UsbDevice device) {
+ mDevice = device;
+ mPort = new Ch340SerialPort(mDevice, 0);
+ }
+
+ @Override
+ public UsbDevice getDevice() {
+ return mDevice;
+ }
+
+ @Override
+ public List getPorts() {
+ return Collections.singletonList(mPort);
+ }
+
+ public class Ch340SerialPort extends CommonUsbSerialPort {
+
+ private static final int USB_TIMEOUT_MILLIS = 5000;
+
+ private final int DEFAULT_BAUD_RATE = 9600;
+
+ private boolean dtr = false;
+ private boolean rts = false;
+
+ private UsbEndpoint mReadEndpoint;
+ private UsbEndpoint mWriteEndpoint;
+
+ public Ch340SerialPort(UsbDevice device, int portNumber) {
+ super(device, portNumber);
+ }
+
+ @Override
+ public UsbSerialDriver getDriver() {
+ return Ch34xSerialDriver.this;
+ }
+
+ @Override
+ public void open(UsbDeviceConnection connection) throws IOException {
+ if (mConnection != null) {
+ throw new IOException("Already opened.");
+ }
+
+ mConnection = connection;
+ boolean opened = false;
+ try {
+ for (int i = 0; i < mDevice.getInterfaceCount(); i++) {
+ UsbInterface usbIface = mDevice.getInterface(i);
+ if (mConnection.claimInterface(usbIface, true)) {
+ Log.d(TAG, "claimInterface " + i + " SUCCESS");
+ } else {
+ Log.d(TAG, "claimInterface " + i + " FAIL");
+ }
+ }
+
+ UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1);
+ for (int i = 0; i < dataIface.getEndpointCount(); i++) {
+ UsbEndpoint ep = dataIface.getEndpoint(i);
+ if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
+ if (ep.getDirection() == UsbConstants.USB_DIR_IN) {
+ mReadEndpoint = ep;
+ } else {
+ mWriteEndpoint = ep;
+ }
+ }
+ }
+
+
+ initialize();
+ setBaudRate(DEFAULT_BAUD_RATE);
+
+ opened = true;
+ } finally {
+ if (!opened) {
+ try {
+ close();
+ } catch (IOException e) {
+ // Ignore IOExceptions during close()
+ }
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (mConnection == null) {
+ throw new IOException("Already closed");
+ }
+
+ // TODO: nothing sended on close, maybe needed?
+
+ try {
+ mConnection.close();
+ } finally {
+ mConnection = null;
+ }
+ }
+
+
+ @Override
+ public int read(byte[] dest, int timeoutMillis) throws IOException {
+ final int numBytesRead;
+ synchronized (mReadBufferLock) {
+ int readAmt = Math.min(dest.length, mReadBuffer.length);
+ numBytesRead = mConnection.bulkTransfer(mReadEndpoint, mReadBuffer, readAmt,
+ timeoutMillis);
+ if (numBytesRead < 0) {
+ // This sucks: we get -1 on timeout, not 0 as preferred.
+ // We *should* use UsbRequest, except it has a bug/api oversight
+ // where there is no way to determine the number of bytes read
+ // in response :\ -- http://b.android.com/28023
+ return 0;
+ }
+ System.arraycopy(mReadBuffer, 0, dest, 0, numBytesRead);
+ }
+ return numBytesRead;
+ }
+
+ @Override
+ public int write(byte[] src, int timeoutMillis) throws IOException {
+ int offset = 0;
+
+ while (offset < src.length) {
+ final int writeLength;
+ final int amtWritten;
+
+ synchronized (mWriteBufferLock) {
+ final byte[] writeBuffer;
+
+ writeLength = Math.min(src.length - offset, mWriteBuffer.length);
+ if (offset == 0) {
+ writeBuffer = src;
+ } else {
+ // bulkTransfer does not support offsets, make a copy.
+ System.arraycopy(src, offset, mWriteBuffer, 0, writeLength);
+ writeBuffer = mWriteBuffer;
+ }
+
+ amtWritten = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, writeLength,
+ timeoutMillis);
+ }
+ if (amtWritten <= 0) {
+ throw new IOException("Error writing " + writeLength
+ + " bytes at offset " + offset + " length=" + src.length);
+ }
+
+ Log.d(TAG, "Wrote amt=" + amtWritten + " attempted=" + writeLength);
+ offset += amtWritten;
+ }
+ return offset;
+ }
+
+ private int controlOut(int request, int value, int index) {
+ final int REQTYPE_HOST_TO_DEVICE = 0x41;
+ return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request,
+ value, index, null, 0, USB_TIMEOUT_MILLIS);
+ }
+
+
+ private int controlIn(int request, int value, int index, byte[] buffer) {
+ final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN;
+ return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request,
+ value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS);
+ }
+
+
+ private void checkState(String msg, int request, int value, int[] expected) throws IOException {
+ byte[] buffer = new byte[expected.length];
+ int ret = controlIn(request, value, 0, buffer);
+
+ if (ret < 0) {
+ throw new IOException("Faild send cmd [" + msg + "]");
+ }
+
+ if (ret != expected.length) {
+ throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]");
+ }
+
+ for (int i = 0; i < expected.length; i++) {
+ if (expected[i] == -1) {
+ continue;
+ }
+
+ int current = buffer[i] & 0xff;
+ if (expected[i] != current) {
+ throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " bytes, but get 0x" + Integer.toHexString(current) + " [" + msg + "]");
+ }
+ }
+ }
+
+ private void writeHandshakeByte() throws IOException {
+ if (controlOut(0xa4, ~((dtr ? 1 << 5 : 0) | (rts ? 1 << 6 : 0)), 0) < 0) {
+ throw new IOException("Faild to set handshake byte");
+ }
+ }
+
+ private void initialize() throws IOException {
+ checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00});
+
+ if (controlOut(0xa1, 0, 0) < 0) {
+ throw new IOException("init failed! #2");
+ }
+
+ setBaudRate(DEFAULT_BAUD_RATE);
+
+ checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00});
+
+ if (controlOut(0x9a, 0x2518, 0x0050) < 0) {
+ throw new IOException("init failed! #5");
+ }
+
+ checkState("init #6", 0x95, 0x0706, new int[]{0xff, 0xee});
+
+ if (controlOut(0xa1, 0x501f, 0xd90a) < 0) {
+ throw new IOException("init failed! #7");
+ }
+
+ setBaudRate(DEFAULT_BAUD_RATE);
+
+ writeHandshakeByte();
+
+ checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, 0xee});
+ }
+
+
+ private void setBaudRate(int baudRate) throws IOException {
+ int[] baud = new int[]{2400, 0xd901, 0x0038, 4800, 0x6402,
+ 0x001f, 9600, 0xb202, 0x0013, 19200, 0xd902, 0x000d, 38400,
+ 0x6403, 0x000a, 115200, 0xcc03, 0x0008};
+
+ for (int i = 0; i < baud.length / 3; i++) {
+ if (baud[i * 3] == baudRate) {
+ int ret = controlOut(0x9a, 0x1312, baud[i * 3 + 1]);
+ if (ret < 0) {
+ throw new IOException("Error setting baud rate. #1");
+ }
+ ret = controlOut(0x9a, 0x0f2c, baud[i * 3 + 2]);
+ if (ret < 0) {
+ throw new IOException("Error setting baud rate. #1");
+ }
+
+ return;
+ }
+ }
+
+
+ throw new IOException("Baud rate " + baudRate + " currently not supported");
+ }
+
+
+ @Override
+ public void setParameters(int baudRate, int dataBits, int stopBits, int parity)
+ throws IOException {
+ setBaudRate(baudRate);
+
+ // TODO databit, stopbit and paraty set not implemented
+ }
+
+ @Override
+ public boolean getCD() throws IOException {
+ return false;
+ }
+
+ @Override
+ public boolean getCTS() throws IOException {
+ return false;
+ }
+
+ @Override
+ public boolean getDSR() throws IOException {
+ return false;
+ }
+
+ @Override
+ public boolean getDTR() throws IOException {
+ return dtr;
+ }
+
+ @Override
+ public void setDTR(boolean value) throws IOException {
+ dtr = value;
+ writeHandshakeByte();
+ }
+
+ @Override
+ public boolean getRI() throws IOException {
+ return false;
+ }
+
+ @Override
+ public boolean getRTS() throws IOException {
+ return rts;
+ }
+
+ @Override
+ public void setRTS(boolean value) throws IOException {
+ rts = value;
+ writeHandshakeByte();
+ }
+
+ @Override
+ public boolean purgeHwBuffers(boolean purgeReadBuffers, boolean purgeWriteBuffers) throws IOException {
+ return true;
+ }
+
+ }
+
+ public static Map getSupportedDevices() {
+ final Map supportedDevices = new LinkedHashMap();
+ supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{
+ UsbId.QINHENG_HL340
+ });
+ return supportedDevices;
+ }
+
+}
\ No newline at end of file
diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java
index ae92a41..ae2e067 100644
--- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java
+++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbId.java
@@ -64,6 +64,9 @@ public final class UsbId {
public static final int VENDOR_PROLIFIC = 0x067b;
public static final int PROLIFIC_PL2303 = 0x2303;
+ public static final int VENDOR_QINHENG = 0x1a86;
+ public static final int QINHENG_HL340 = 0x7523;
+
private UsbId() {
throw new IllegalAccessError("Non-instantiable class.");
}
diff --git a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java
index 5fa935c..333af65 100644
--- a/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java
+++ b/usbSerialForAndroid/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java
@@ -51,6 +51,7 @@ public class UsbSerialProber {
probeTable.addDriver(Cp21xxSerialDriver.class);
probeTable.addDriver(FtdiSerialDriver.class);
probeTable.addDriver(ProlificSerialDriver.class);
+ probeTable.addDriver(Ch34xSerialDriver.class);
return probeTable;
}