49 | * This class used to associate {@link UsbI2cAdapter} with supported USB devices. 50 | * Each {@link UsbI2cAdapter} implementation must declare its supported USB device identifiers 51 | * using the static method {@code getSupportedUsbDeviceIdentifiers()}. 52 | *
53 | * @see UsbDevice#getVendorId() 54 | * @see UsbDevice#getProductId() 55 | */ 56 | public static class UsbDeviceIdentifier { 57 | private final int vendorId; 58 | private final int productId; 59 | 60 | public UsbDeviceIdentifier(int vendorId, int productId) { 61 | this.vendorId = vendorId; 62 | this.productId = productId; 63 | } 64 | 65 | /** 66 | * Get USB device vendor identifier. 67 | * @return USB device vendor identifier 68 | */ 69 | public int getVendorId() { 70 | return vendorId; 71 | } 72 | 73 | /** 74 | * Get USB device product identifier. 75 | * @return USB device product identifier 76 | */ 77 | public int getProductId() { 78 | return productId; 79 | } 80 | 81 | @Override 82 | public boolean equals(Object o) { 83 | if (this == o) return true; 84 | if (o == null || getClass() != o.getClass()) return false; 85 | UsbDeviceIdentifier that = (UsbDeviceIdentifier) o; 86 | return vendorId == that.vendorId && productId == that.productId; 87 | } 88 | 89 | @Override 90 | public int hashCode() { 91 | return Objects.hash(vendorId, productId); 92 | } 93 | } 94 | 95 | private UsbI2cManager(UsbManager usbManager, 96 | List206 | * If this method not called, list returned by 207 | * {@link #getSupportedUsbI2cAdapters()} used by default. 208 | *
209 | * @param usbI2cAdapters list of USB I2C adapters to use 210 | * @return this Builder object 211 | */ 212 | public Builder setAdapters(List
36 | * Data Sheet
37 | */
38 | public class Ch341UsbI2cAdapter extends BaseUsbI2cAdapter {
39 | // Adapter name
40 | public static final String ADAPTER_NAME = "CH341";
41 |
42 | private static final int CH341_I2C_LOW_SPEED = 0; // low speed - 20kHz
43 | private static final int CH341_I2C_STANDARD_SPEED = 1; // standard speed - 100kHz
44 | private static final int CH341_I2C_FAST_SPEED = 2; // fast speed - 400kHz
45 | private static final int CH341_I2C_HIGH_SPEED = 3; // high speed - 750kHz
46 |
47 | private static final int CH341_CMD_I2C_STREAM = 0xAA;
48 |
49 | private static final int CH341_CMD_I2C_STM_STA = 0x74;
50 | private static final int CH341_CMD_I2C_STM_STO = 0x75;
51 | private static final int CH341_CMD_I2C_STM_OUT = 0x80;
52 | private static final int CH341_CMD_I2C_STM_IN = 0xC0;
53 | private static final int CH341_CMD_I2C_STM_SET = 0x60;
54 | private static final int CH341_CMD_I2C_STM_END = 0x00;
55 |
56 | // CH341 max transfer size
57 | private static final int MAX_TRANSFER_SIZE = 32;
58 |
59 | private final byte[] writeBuffer = new byte[MAX_TRANSFER_SIZE];
60 | private final byte[] readBuffer = new byte[MAX_TRANSFER_SIZE];
61 |
62 | class Ch341UsbI2cDevice extends BaseUsbI2cDevice {
63 | Ch341UsbI2cDevice(int address) {
64 | super(address);
65 | }
66 |
67 | @Override
68 | protected void deviceReadReg(int reg, byte[] buffer, int length) throws IOException {
69 | readRegData(address, reg, buffer, length);
70 | }
71 |
72 | @Override
73 | protected void deviceRead(byte[] buffer, int length) throws IOException {
74 | readData(address, buffer, length);
75 | }
76 |
77 | @Override
78 | protected void deviceWrite(byte[] buffer, int length) throws IOException {
79 | writeData(address, buffer, length);
80 | }
81 | }
82 |
83 |
84 | public Ch341UsbI2cAdapter(UsbI2cManager i2cManager, UsbDevice usbDevice) {
85 | super(i2cManager, usbDevice);
86 | }
87 |
88 | @Override
89 | public String getName() {
90 | return ADAPTER_NAME;
91 | }
92 |
93 | @Override
94 | protected BaseUsbI2cDevice getDeviceImpl(int address) {
95 | return new Ch341UsbI2cDevice(address);
96 | }
97 |
98 | @Override
99 | protected void init(UsbDevice usbDevice) throws IOException {
100 | if (usbDevice.getInterfaceCount() == 0) {
101 | throw new IOException("No interfaces found for device: " + usbDevice);
102 | }
103 |
104 | UsbInterface usbInterface = usbDevice.getInterface(0);
105 | if (usbInterface.getEndpointCount() < 2) {
106 | throw new IOException("No endpoints found for device: " + usbDevice);
107 | }
108 |
109 | UsbEndpoint usbReadEndpoint = null, usbWriteEndpoint = null;
110 | for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
111 | UsbEndpoint usbEndpoint = usbInterface.getEndpoint(i);
112 | if (usbEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
113 | if (usbEndpoint.getDirection() == UsbConstants.USB_DIR_IN) {
114 | usbReadEndpoint = usbEndpoint;
115 | } else {
116 | usbWriteEndpoint = usbEndpoint;
117 | }
118 | }
119 | }
120 | if (usbReadEndpoint == null || usbWriteEndpoint == null) {
121 | throw new IOException("No read or write bulk endpoint found for device: " + usbDevice);
122 | }
123 | setBulkEndpoints(usbReadEndpoint, usbWriteEndpoint);
124 |
125 | configure();
126 | }
127 |
128 | protected int getClockSpeedConstant(int speed) {
129 | switch (speed) {
130 | case 20000:
131 | return CH341_I2C_LOW_SPEED;
132 | case CLOCK_SPEED_STANDARD:
133 | return CH341_I2C_STANDARD_SPEED;
134 | case CLOCK_SPEED_FAST:
135 | return CH341_I2C_FAST_SPEED;
136 | case 750000:
137 | return CH341_I2C_HIGH_SPEED;
138 | }
139 |
140 | return -1;
141 | }
142 |
143 | @Override
144 | public boolean isClockSpeedSupported(int speed) {
145 | return (getClockSpeedConstant(speed) >= 0);
146 | }
147 |
148 | protected void configure() throws IOException {
149 | writeBuffer[0] = (byte) CH341_CMD_I2C_STREAM;
150 | writeBuffer[1] = (byte) (CH341_CMD_I2C_STM_SET | getClockSpeedConstant(getClockSpeed()));
151 | writeBuffer[2] = CH341_CMD_I2C_STM_END;
152 | writeBulkData(writeBuffer, 3);
153 | }
154 |
155 | protected void checkDataLength(int length, int bufferDataLength) {
156 | super.checkDataLength(length, Math.min(MAX_TRANSFER_SIZE - 6, bufferDataLength));
157 | }
158 |
159 | private void writeData(int address, byte[] data, int length) throws IOException {
160 | checkDataLength(length, data.length);
161 | int i = 0;
162 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STREAM;
163 | writeBuffer[i++] = CH341_CMD_I2C_STM_STA; // START condition
164 | writeBuffer[i++] = (byte) (CH341_CMD_I2C_STM_OUT | (length + 1)); // data length + 1
165 | writeBuffer[i++] = getAddressByte(address, false);
166 | System.arraycopy(data, 0, writeBuffer, i, length);
167 | i += length;
168 | writeBuffer[i++] = CH341_CMD_I2C_STM_STO; // STOP condition
169 | writeBuffer[i++] = CH341_CMD_I2C_STM_END; // end of transaction
170 | writeBulkData(writeBuffer, i);
171 | }
172 |
173 | private void readData(int address, byte[] data, int length) throws IOException {
174 | checkDataLength(length, data.length);
175 | checkDevicePresence(address); // to avoid weird phantom devices in scan results
176 | int i = 0;
177 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STREAM;
178 | writeBuffer[i++] = CH341_CMD_I2C_STM_STA; // START condition
179 | writeBuffer[i++] = (byte) (CH341_CMD_I2C_STM_OUT | 1); // zero data length + 1
180 | writeBuffer[i++] = getAddressByte(address, true);
181 | if (length > 0) {
182 | for (int j = 0; j < length - 1; j++) {
183 | writeBuffer[i++] = (byte) (CH341_CMD_I2C_STM_IN | 1);
184 | }
185 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STM_IN;
186 | }
187 | writeBuffer[i++] = CH341_CMD_I2C_STM_STO; // STOP condition
188 | writeBuffer[i++] = CH341_CMD_I2C_STM_END; // end of transaction
189 | transferBulkData(writeBuffer, i, data, length);
190 | }
191 |
192 | private void readRegData(int address, int reg, byte[] data, int length) throws IOException {
193 | checkDataLength(length, data.length);
194 | // Write register number
195 | int i = 0;
196 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STREAM;
197 | writeBuffer[i++] = CH341_CMD_I2C_STM_STA; // START condition
198 | writeBuffer[i++] = (byte) (CH341_CMD_I2C_STM_OUT | 2); // reg data length + 1
199 | writeBuffer[i++] = getAddressByte(address, false);
200 | writeBuffer[i++] = (byte) reg;
201 | writeBulkData(writeBuffer, i);
202 | // Read register data
203 | i = 0;
204 | writeBuffer[i++]= (byte) CH341_CMD_I2C_STREAM;
205 | writeBuffer[i++] = CH341_CMD_I2C_STM_STA; // START condition
206 | writeBuffer[i++] = (byte) (CH341_CMD_I2C_STM_OUT | 1); // zero data length + 1
207 | writeBuffer[i++] = getAddressByte(address, true);
208 | if (length > 0) {
209 | for (int j = 0; j < length - 1; j++) {
210 | writeBuffer[i++] = (byte) (CH341_CMD_I2C_STM_IN | 1);
211 | }
212 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STM_IN;
213 | }
214 | writeBuffer[i++] = CH341_CMD_I2C_STM_STO; // STOP condition
215 | writeBuffer[i++] = CH341_CMD_I2C_STM_END; // end of transaction
216 | transferBulkData(writeBuffer, i, data, length);
217 | }
218 |
219 | private void checkDevicePresence(int address) throws IOException {
220 | int i = 0;
221 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STREAM;
222 | writeBuffer[i++] = CH341_CMD_I2C_STM_STA; // START condition
223 | writeBuffer[i++] = (byte) CH341_CMD_I2C_STM_OUT;
224 | writeBuffer[i++] = getAddressByte(address, true);
225 | writeBuffer[i++] = CH341_CMD_I2C_STM_STO; // STOP condition
226 | writeBuffer[i++] = CH341_CMD_I2C_STM_END; // end of transaction
227 | int res = transferBulkData(writeBuffer, i, writeBuffer, 1);
228 | if (res <= 0 || (writeBuffer[0] & 0x80) != 0) {
229 | throw new IOException(String.format("No device present at address 0x%02x", address));
230 | }
231 | }
232 |
233 | /**
234 | * Write request and read response, if needed.
235 | *
236 | * @param writeData data buffer to write data
237 | * @param writeLength data length to write
238 | * @param readData data buffer to read data
239 | * @param readLength data length to read (can be zero)
240 | * @return actual length of read data
241 | * @throws IOException in case of data read/write error or timeout
242 | */
243 | private int transferBulkData(byte[] writeData, int writeLength,
244 | byte[] readData, int readLength) throws IOException {
245 | writeBulkData(writeData, writeLength);
246 | if (readLength > 0) {
247 | readLength = readBulkData(readBuffer, MAX_TRANSFER_SIZE);
248 | System.arraycopy(readBuffer, 0, readData, 0, readLength);
249 | }
250 | return readLength;
251 | }
252 |
253 | private int readBulkData(byte[] data, int length) throws IOException {
254 | return bulkRead(data, 0, length, USB_TIMEOUT_MILLIS);
255 | }
256 |
257 | private void writeBulkData(byte[] data, int length) throws IOException {
258 | bulkWrite(data, length, USB_TIMEOUT_MILLIS);
259 | }
260 |
261 | public static UsbDeviceIdentifier[] getSupportedUsbDeviceIdentifiers() {
262 | return new UsbDeviceIdentifier[] {
263 | new UsbDeviceIdentifier(0x1a86, 0x5512)
264 | };
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/github/ykc3/android/usbi2c/adapter/Cp2112UsbI2cAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019 Victor Antonovich
33 | * Data Sheet
34 | *
35 | * Programming Interface Specification
36 | */
37 | public class Cp2112UsbI2cAdapter extends HidUsbI2cAdapter {
38 | // Adapter name
39 | public static final String ADAPTER_NAME = "CP2112";
40 |
41 | // CP2112 report IDs
42 | private static final int REPORT_ID_SMBUS_CONFIG = 0x06;
43 | private static final int REPORT_ID_DATA_READ_REQUEST = 0x10;
44 | private static final int REPORT_ID_DATA_WRITE_READ_REQUEST = 0x11;
45 | private static final int REPORT_ID_DATA_READ_FORCE_SEND = 0x12;
46 | private static final int REPORT_ID_DATA_READ_RESPONSE = 0x13;
47 | private static final int REPORT_ID_DATA_WRITE_REQUEST = 0x14;
48 | private static final int REPORT_ID_TRANSFER_STATUS_REQUEST = 0x15;
49 | private static final int REPORT_ID_TRANSFER_STATUS_RESPONSE = 0x16;
50 | private static final int REPORT_ID_CANCEL_TRANSFER = 0x17;
51 |
52 | // CP2112 SMBus configuration size (including ReportID)
53 | private static final int SMBUS_CONFIG_SIZE = 14;
54 |
55 | // CP2112 SMBus configuration: Clock Speed
56 | private static final int SMBUS_CONFIG_CLOCK_SPEED_OFFSET = 1;
57 | // CP2112 SMBus configuration: Retry Time
58 | private static final int SMBUS_CONFIG_RETRY_TIME_OFFSET = 12;
59 |
60 | // CP2112 max I2C data write length (single write transfer)
61 | private static final int MAX_DATA_WRITE_LENGTH = 61;
62 |
63 | // CP2112 max I2C data read length (multiple read transfers)
64 | private static final int MAX_DATA_READ_LENGTH = 512;
65 |
66 | private static final int NUM_DRAIN_DATA_REPORTS_RETRIES = 10;
67 |
68 | // CP2112 max data transfer status reads while waiting for transfer completion
69 | private static final int NUM_TRANSFER_STATUS_RETRIES = 10;
70 |
71 | // CP2112 transfer status codes
72 | private static final int TRANSFER_STATUS_IDLE = 0x00;
73 | private static final int TRANSFER_STATUS_BUSY = 0x01;
74 | private static final int TRANSFER_STATUS_COMPLETE = 0x02;
75 | private static final int TRANSFER_STATUS_ERROR = 0x03;
76 |
77 | // CP2112 min clock speed
78 | private static final int MIN_CLOCK_SPEED = 10000;
79 | // CP2112 max clock speed
80 | private static final int MAX_CLOCK_SPEED = CLOCK_SPEED_HIGH;
81 |
82 | class Cp2112UsbI2cDevice extends BaseUsbI2cDevice {
83 | Cp2112UsbI2cDevice(int address) {
84 | super(address);
85 | }
86 |
87 | @Override
88 | protected void deviceReadReg(int reg, byte[] buffer, int length) throws IOException {
89 | readRegData(address, reg, buffer, length);
90 | }
91 |
92 | @Override
93 | protected void deviceRead(byte[] buffer, int length) throws IOException {
94 | readData(address, buffer, length);
95 | }
96 |
97 | @Override
98 | protected void deviceWrite(byte[] buffer, int length) throws IOException {
99 | writeData(address, buffer, length);
100 | }
101 | }
102 |
103 | public Cp2112UsbI2cAdapter(UsbI2cManager manager, UsbDevice usbDevice) {
104 | super(manager, usbDevice);
105 | }
106 |
107 | @Override
108 | public String getName() {
109 | return ADAPTER_NAME;
110 | }
111 |
112 | @Override
113 | protected BaseUsbI2cDevice getDeviceImpl(int address) {
114 | return new Cp2112UsbI2cDevice(address);
115 | }
116 |
117 | @Override
118 | protected void init(UsbDevice usbDevice) throws IOException {
119 | super.init(usbDevice);
120 | // Drain all stale data reports
121 | drainPendingDataReports();
122 | }
123 |
124 | protected void configure() throws IOException {
125 | // Get current config
126 | getHidFeatureReport(REPORT_ID_SMBUS_CONFIG, buffer, SMBUS_CONFIG_SIZE);
127 |
128 | // Clock Speed (in Hertz, default 0x000186A0 - 100 kHz)
129 | int clockSpeed = getClockSpeed();
130 | buffer[SMBUS_CONFIG_CLOCK_SPEED_OFFSET] = (byte) (clockSpeed >> 24);
131 | buffer[SMBUS_CONFIG_CLOCK_SPEED_OFFSET + 1] = (byte) (clockSpeed >> 16);
132 | buffer[SMBUS_CONFIG_CLOCK_SPEED_OFFSET + 2] = (byte) (clockSpeed >> 8);
133 | buffer[SMBUS_CONFIG_CLOCK_SPEED_OFFSET + 3] = (byte) clockSpeed;
134 |
135 | // Retry Time (number of retries, default 0 - no limit)
136 | buffer[SMBUS_CONFIG_RETRY_TIME_OFFSET] = 0x00;
137 | buffer[SMBUS_CONFIG_RETRY_TIME_OFFSET + 1] = 0x01;
138 |
139 | setHidFeatureReport(buffer, SMBUS_CONFIG_SIZE);
140 | }
141 |
142 | @Override
143 | public boolean isClockSpeedSupported(int speed) {
144 | return (speed >= MIN_CLOCK_SPEED && speed <= MAX_CLOCK_SPEED);
145 | }
146 |
147 | private void checkWriteDataLength(int length, int bufferLength) {
148 | checkDataLength(length, Math.min(MAX_DATA_WRITE_LENGTH, bufferLength));
149 | }
150 |
151 | private void checkReadDataLength(int length, int bufferLength) {
152 | checkDataLength(length, Math.min(MAX_DATA_READ_LENGTH, bufferLength));
153 | }
154 |
155 | private void writeData(int address, byte[] data, int length) throws IOException {
156 | checkWriteDataLength(length, data.length);
157 | buffer[0] = REPORT_ID_DATA_WRITE_REQUEST;
158 | buffer[1] = getAddressByte(address, false);
159 | buffer[2] = (byte) length;
160 | System.arraycopy(data, 0, buffer, 3, length);
161 | sendHidDataReport(buffer, length + 3);
162 | waitTransferComplete();
163 | }
164 |
165 | private void readData(int address, byte[] data, int length) throws IOException {
166 | checkReadDataLength(length, data.length);
167 | buffer[0] = REPORT_ID_DATA_READ_REQUEST;
168 | buffer[1] = getAddressByte(address, false); // read bit is not required
169 | buffer[2] = (byte) (length >> 8);
170 | buffer[3] = (byte) length;
171 | sendHidDataReport(buffer, 4);
172 | waitTransferComplete();
173 | readDataFully(data, length);
174 | }
175 |
176 | private void readRegData(int address, int reg, byte[] data, int length) throws IOException {
177 | checkReadDataLength(length, data.length);
178 | buffer[0] = REPORT_ID_DATA_WRITE_READ_REQUEST;
179 | buffer[1] = getAddressByte(address, false); // read bit is not required
180 | buffer[2] = (byte) (length >> 8);
181 | buffer[3] = (byte) length;
182 | buffer[4] = 0x01; // number of bytes in target address (register ID)
183 | buffer[5] = (byte) reg;
184 | sendHidDataReport(buffer, 6);
185 | waitTransferComplete();
186 | readDataFully(data, length);
187 | }
188 |
189 | private void readDataFully(byte[] data, int length) throws IOException {
190 | int totalReadLen = 0;
191 | while (totalReadLen < length) {
192 | sendForceReadDataRequest(length - totalReadLen);
193 |
194 | getHidDataReport(buffer, USB_TIMEOUT_MILLIS);
195 |
196 | if (buffer[0] != REPORT_ID_DATA_READ_RESPONSE) {
197 | throw new IOException(String.format("Unexpected data read report ID: 0x%02x",
198 | buffer[0]));
199 | }
200 |
201 | if (buffer[1] == TRANSFER_STATUS_ERROR) {
202 | throw new IOException(String.format("Data read status error, condition: 0x%02x",
203 | buffer[2]));
204 | }
205 |
206 | int lastReadLen = buffer[2] & 0xff;
207 | if (lastReadLen > length - totalReadLen) {
208 | throw new IOException(String.format("Too many data read: " +
209 | "%d byte(s), expected: %d byte(s)", lastReadLen, length - totalReadLen));
210 | }
211 |
212 | System.arraycopy(buffer, 3, data, totalReadLen, lastReadLen);
213 |
214 | totalReadLen += lastReadLen;
215 | }
216 | }
217 |
218 | private void waitTransferComplete() throws IOException {
219 | int tryNum = 1;
220 | while (tryNum++ <= NUM_TRANSFER_STATUS_RETRIES) {
221 | sendTransferStatusRequest();
222 |
223 | getHidDataReport(buffer, USB_TIMEOUT_MILLIS);
224 |
225 | if (buffer[0] != REPORT_ID_TRANSFER_STATUS_RESPONSE) {
226 | throw new IOException(String.format("Unexpected transfer status report ID: 0x%02x",
227 | buffer[0]));
228 | }
229 |
230 | switch (buffer[1]) {
231 | case TRANSFER_STATUS_BUSY:
232 | continue;
233 | case TRANSFER_STATUS_COMPLETE:
234 | return;
235 | default:
236 | throw new IOException(String.format("Invalid transfer status: 0x%02x",
237 | buffer[1]));
238 | }
239 | }
240 | // Retries limit was reached and TRANSFER_STATUS_COMPLETE status is not reached
241 | cancelTransfer();
242 | throw new IOException("Transfer retries limit reached");
243 | }
244 |
245 | private void sendForceReadDataRequest(int length) throws IOException {
246 | buffer[0] = REPORT_ID_DATA_READ_FORCE_SEND;
247 | buffer[1] = (byte) ((length >> 8) & 0xff);
248 | buffer[2] = (byte) length;
249 | sendHidDataReport(buffer, 3);
250 | }
251 |
252 | private void sendTransferStatusRequest() throws IOException {
253 | buffer[0] = REPORT_ID_TRANSFER_STATUS_REQUEST;
254 | buffer[1] = 0x01;
255 | sendHidDataReport(buffer, 2);
256 | }
257 |
258 | private void cancelTransfer() throws IOException {
259 | buffer[0] = REPORT_ID_CANCEL_TRANSFER;
260 | buffer[1] = 0x01;
261 | sendHidDataReport(buffer, 2);
262 | }
263 |
264 | private void drainPendingDataReports() throws IOException {
265 | int tryNum = 1;
266 | while (tryNum++ <= NUM_DRAIN_DATA_REPORTS_RETRIES) {
267 | try {
268 | getHidDataReport(buffer, 5);
269 | } catch (IOException e) {
270 | break;
271 | }
272 | }
273 | if (tryNum >= NUM_DRAIN_DATA_REPORTS_RETRIES) {
274 | throw new IOException("Can't drain pending data reports");
275 | }
276 | cancelTransfer();
277 | }
278 |
279 | public static UsbDeviceIdentifier[] getSupportedUsbDeviceIdentifiers() {
280 | return new UsbDeviceIdentifier[] {
281 | new UsbDeviceIdentifier(0x10c4, 0xea90)
282 | };
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/github/ykc3/android/usbi2c/adapter/Ft232hUsbI2cAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2021 Victor Antonovich
36 | * Data Sheet
37 | *
38 | * MPSSE Basics
39 | *
40 | * Command Processor for MPSSE and MCU Host Bus Emulation Modes
41 | *
42 | * USB to I2C Example using the FT232H and FT201X devices
43 | *
44 | */
45 | public class Ft232hUsbI2cAdapter extends BaseUsbI2cAdapter {
46 | // Adapter name
47 | public static final String ADAPTER_NAME = "FT232H";
48 |
49 | // Nanoseconds in one millisecond
50 | private static final long NANOS_IN_MS = 1000000L;
51 |
52 | // FT232H control requests
53 | private static final int SIO_RESET_REQUEST = 0x00;
54 | private static final int SIO_SET_EVENT_CHAR_REQUEST = 0x06;
55 | private static final int SIO_SET_ERROR_CHAR_REQUEST = 0x07;
56 | private static final int SIO_SET_LATENCY_TIMER_REQUEST = 0x09;
57 | private static final int SIO_SET_BITMODE_REQUEST = 0x0B;
58 |
59 | // FT232H control request parameters
60 | private static final int SIO_RESET_SIO = 0;
61 | private static final int SIO_RESET_PURGE_RX = 1;
62 | private static final int SIO_RESET_PURGE_TX = 2;
63 |
64 | // FT232H bit modes
65 | private static final int BITMODE_RESET = 0x00;
66 | private static final int BITMODE_MPSSE = 0x02;
67 |
68 | // FT232H AD port I2C lines bit masks
69 | private static final int I2C_SCL_BIT = 0x01; // AD0
70 | private static final int I2C_SDA_O_BIT = 0x02; // AD1
71 | private static final int I2C_SDA_I_BIT = 0x04; // AD2
72 |
73 | // FT232H USB latency timer (in milliseconds)
74 | private static final int LATENCY_TIMER = 16;
75 |
76 | // FT232H min clock speed
77 | private static final int MIN_CLOCK_SPEED = 10000;
78 | // FT232H max clock speed
79 | private static final int MAX_CLOCK_SPEED = CLOCK_SPEED_HIGH;
80 | // FT232H internal bus clock speed
81 | private static final int BUS_CLOCK_SPEED = 30000000;
82 |
83 | // FT232H max transfer size
84 | private static final int MAX_TRANSFER_SIZE = 16384;
85 |
86 | // FT232H response packet header length
87 | private static final int READ_PACKET_HEADER_LENGTH = 2;
88 |
89 | private final byte[] readBuffer = new byte[MAX_TRANSFER_SIZE];
90 |
91 | private final Ft232Mpsse mpsse = new Ft232Mpsse();
92 |
93 | class Ft232Mpsse {
94 | // MPSSE commands
95 | private static final int MPSSE_WRITE_BYTES_NVE_MSB = 0x11;
96 | private static final int MPSSE_WRITE_BITS_NVE_MSB = 0x13;
97 | private static final int MPSSE_READ_BYTES_PVE_MSB = 0x20;
98 | private static final int MPSSE_READ_BITS_PVE_MSB = 0x22;
99 | private static final int MPSSE_SET_BITS_LOW = 0x80;
100 | private static final int MPSSE_LOOPBACK_END = 0x85;
101 | private static final int MPSSE_SET_TCK_DIVISOR = 0x86;
102 | private static final int MPSSE_SEND_IMMEDIATE = 0x87;
103 | private static final int MPSSE_DISABLE_CLK_DIV5 = 0x8A;
104 | private static final int MPSSE_ENABLE_CLK_3PHASE = 0x8C;
105 | private static final int MPSSE_DISABLE_CLK_ADAPTIVE = 0x97;
106 | private static final int MPSSE_DRIVE_ZERO = 0x9E;
107 | private static final int MPSSE_DUMMY_REQUEST = 0xAA;
108 |
109 | // MPSSE error response
110 | private static final int MPSSE_ERROR = 0xFA;
111 |
112 | // Number of writes to port to ensure port state is steady
113 | private static final int PORT_WRITE_STEADY_COUNT = 4;
114 |
115 | private final byte[] buffer = new byte[MAX_TRANSFER_SIZE];
116 | private int index;
117 |
118 | public void bufferClear() {
119 | index = 0;
120 | }
121 |
122 | private void bufferCheck(int length) {
123 | if (index + length > buffer.length) {
124 | throw new IllegalArgumentException(String.format("MPSSE buffer overflow: " +
125 | "requested %d, available %d", length, buffer.length - index));
126 | }
127 | }
128 |
129 | public void bufferWrite() throws IOException {
130 | try {
131 | dataWrite(buffer, index);
132 | } finally {
133 | bufferClear();
134 | }
135 | }
136 |
137 | public void checkEnabled() throws IOException {
138 | bufferClear();
139 | buffer[index++] = (byte) MPSSE_DUMMY_REQUEST;
140 | bufferWrite();
141 | dataRead(buffer, 2);
142 | if ((buffer[0] & 0xFF) != MPSSE_ERROR || (buffer[1] & 0xFF) != MPSSE_DUMMY_REQUEST) {
143 | throw new IOException("MPSSE is not enabled");
144 | }
145 | }
146 |
147 | public void i2cInit(int clockSpeed) {
148 | bufferCheck(10);
149 | // Configure clock mode for I2C
150 | buffer[index++] = (byte) MPSSE_DISABLE_CLK_DIV5;
151 | buffer[index++] = (byte) MPSSE_DISABLE_CLK_ADAPTIVE;
152 | buffer[index++] = (byte) MPSSE_ENABLE_CLK_3PHASE;
153 | // Configure drive-zero (open drain) mode for I2C pins
154 | buffer[index++] = (byte) MPSSE_DRIVE_ZERO;
155 | buffer[index++] = (byte) (I2C_SCL_BIT | I2C_SDA_O_BIT | I2C_SDA_I_BIT); // port AD
156 | buffer[index++] = 0; // port AC
157 | // Disable loopback
158 | buffer[index++] = (byte) MPSSE_LOOPBACK_END;
159 | // Set I2c speed clock divisor
160 | int divisor = ((2 * BUS_CLOCK_SPEED / clockSpeed) - 2) / 3;
161 | buffer[index++] = (byte) MPSSE_SET_TCK_DIVISOR;
162 | buffer[index++] = (byte) (divisor);
163 | buffer[index++] = (byte) (divisor >> 8);
164 | }
165 |
166 | public void i2cPortConfig(int levels) {
167 | bufferCheck(3);
168 | buffer[index++] = (byte) MPSSE_SET_BITS_LOW;
169 | buffer[index++] = (byte) levels; // bits set are driven to high level
170 | buffer[index++] = (byte) ~I2C_SDA_I_BIT; // bits set are outputs
171 | }
172 |
173 | public void i2cIdle() {
174 | // Drive both SDA and SCL outputs to high level
175 | i2cPortConfig(0xFF);
176 | }
177 |
178 | public void i2cStart() {
179 | // Bring SDA low while SCL is high
180 | for (int i = 0; i < PORT_WRITE_STEADY_COUNT; i++) {
181 | i2cPortConfig(~I2C_SDA_O_BIT);
182 | }
183 | // Bring both SDA and SCL low
184 | for (int i = 0; i < PORT_WRITE_STEADY_COUNT; i++) {
185 | i2cPortConfig(~(I2C_SDA_O_BIT | I2C_SCL_BIT));
186 | }
187 | }
188 |
189 | public void i2cSendByteAndReadAck(byte value) {
190 | bufferCheck(4);
191 | buffer[index++] = MPSSE_WRITE_BYTES_NVE_MSB; // clock bytes out MSB first on clock falling edge
192 | buffer[index++] = 0x00; //
193 | buffer[index++] = 0x00; // 0x0000 - send one byte
194 | buffer[index++] = value; // byte value to send
195 | i2cPortConfig(~I2C_SCL_BIT); // put into transfer idle state: SCK low, SDA high
196 | bufferCheck(2);
197 | buffer[index++] = MPSSE_READ_BITS_PVE_MSB; // clock bits in MSB first on clock rising edge
198 | buffer[index++] = 0x00; // clock in one bit (ACK)
199 | }
200 |
201 | public void i2cWriteByteAndCheckAck(byte value) throws IOException {
202 | mpsse.i2cSendByteAndReadAck(value);
203 | mpsse.i2cReceiveImmediate();
204 | mpsse.bufferWrite();
205 | dataRead(buffer, 1);
206 | if ((buffer[0] & 0x01) != 0) {
207 | throw new IOException("NACK from slave");
208 | }
209 | }
210 |
211 | public void i2cReadByte(boolean isAck) {
212 | bufferCheck(6);
213 | buffer[index++] = MPSSE_READ_BYTES_PVE_MSB; // clock bytes in MSB first on clock rising edge
214 | buffer[index++] = 0x00;
215 | buffer[index++] = 0x00; // 0x0000 - receive one byte
216 | buffer[index++] = MPSSE_WRITE_BITS_NVE_MSB; // clock bits out MSB first on clock falling edge
217 | buffer[index++] = 0x00;// clock out one bit
218 | buffer[index++] = (byte) (isAck ? 0x00 : 0xFF);
219 | i2cPortConfig(~I2C_SCL_BIT); // put into transfer idle state: SCK low, SDA high
220 | }
221 |
222 | public void i2cReadBytes(int length) {
223 | for (int i = 0; i < length; i++) {
224 | i2cReadByte(i < (length - 1));
225 | }
226 | }
227 |
228 | public void i2cStop() {
229 | // Bring SDA low and keep SCL low
230 | for (int i = 0; i < PORT_WRITE_STEADY_COUNT; i++) {
231 | i2cPortConfig(~(I2C_SDA_O_BIT | I2C_SCL_BIT));
232 | }
233 | // Bring SCL high and keep SDA low
234 | for (int i = 0; i < PORT_WRITE_STEADY_COUNT; i++) {
235 | i2cPortConfig(~I2C_SDA_O_BIT);
236 | }
237 | // Bring SDA high while SCL is high
238 | for (int i = 0; i < PORT_WRITE_STEADY_COUNT; i++) {
239 | i2cPortConfig(0xFF);
240 | }
241 | }
242 |
243 | public void i2cReceiveImmediate() {
244 | bufferCheck(1);
245 | buffer[index++] = (byte) MPSSE_SEND_IMMEDIATE;
246 | }
247 | }
248 |
249 | class Ft232hUsbI2cDevice extends BaseUsbI2cDevice {
250 | Ft232hUsbI2cDevice(int address) {
251 | super(address);
252 | }
253 |
254 | @Override
255 | protected void deviceReadReg(int reg, byte[] buffer, int length) throws IOException {
256 | readRegData(address, reg, buffer, length);
257 | }
258 |
259 | @Override
260 | protected void deviceWrite(byte[] buffer, int length) throws IOException {
261 | writeData(address, buffer, length);
262 | }
263 |
264 | @Override
265 | protected void deviceRead(byte[] buffer, int length) throws IOException {
266 | readData(address, buffer, length);
267 | }
268 | }
269 |
270 | public Ft232hUsbI2cAdapter(UsbI2cManager manager, UsbDevice device) {
271 | super(manager, device);
272 | }
273 |
274 | @Override
275 | public String getName() {
276 | return ADAPTER_NAME;
277 | }
278 |
279 | @Override
280 | protected BaseUsbI2cDevice getDeviceImpl(int address) {
281 | return new Ft232hUsbI2cDevice(address);
282 | }
283 |
284 | @Override
285 | protected void init(UsbDevice usbDevice) throws IOException {
286 | if (usbDevice.getInterfaceCount() == 0) {
287 | throw new IOException("No interfaces found for device: " + usbDevice);
288 | }
289 |
290 | UsbInterface usbInterface = usbDevice.getInterface(0);
291 | if (usbInterface.getEndpointCount() < 2) {
292 | throw new IOException("No endpoints found for device: " + usbDevice);
293 | }
294 | setBulkEndpoints(usbInterface.getEndpoint(0), usbInterface.getEndpoint(1));
295 |
296 | configure();
297 | }
298 |
299 | @Override
300 | public boolean isClockSpeedSupported(int speed) {
301 | return (speed >= MIN_CLOCK_SPEED && speed <= MAX_CLOCK_SPEED);
302 | }
303 |
304 | protected void configure() throws IOException {
305 | // Reset device
306 | commandWrite(SIO_RESET_REQUEST, SIO_RESET_SIO);
307 | // Set latency timer
308 | commandWrite(SIO_SET_LATENCY_TIMER_REQUEST, LATENCY_TIMER);
309 | // Drain RX and TX buffers
310 | commandWrite(SIO_RESET_REQUEST, SIO_RESET_PURGE_RX);
311 | commandWrite(SIO_RESET_REQUEST, SIO_RESET_PURGE_TX);
312 | // Disable event and error characters
313 | commandWrite(SIO_SET_EVENT_CHAR_REQUEST, 0);
314 | commandWrite(SIO_SET_ERROR_CHAR_REQUEST, 0);
315 | // Enable MPSSE mode
316 | commandWrite(SIO_SET_BITMODE_REQUEST, BITMODE_RESET << 8);
317 | commandWrite(SIO_SET_BITMODE_REQUEST, BITMODE_MPSSE << 8);
318 | // Check MPSSE mode is enabled
319 | mpsse.checkEnabled();
320 | // Init MPSSE for I2C
321 | mpsse.i2cInit(getClockSpeed());
322 | // Set I2C idle state
323 | mpsse.i2cIdle();
324 | // Write MPSSE commands to FT232H
325 | mpsse.bufferWrite();
326 | }
327 |
328 | private void checkReadDataLength(int length, int dataBufferLength) {
329 | checkDataLength(length, Math.min(readBuffer.length
330 | - READ_PACKET_HEADER_LENGTH, dataBufferLength));
331 | }
332 |
333 | private void readRegData(int address, int reg, byte[] data, int length) throws IOException {
334 | checkReadDataLength(length, data.length);
335 | mpsse.bufferClear();
336 | mpsse.i2cIdle();
337 | mpsse.i2cStart();
338 | mpsse.i2cWriteByteAndCheckAck(getAddressByte(address, false));
339 | mpsse.i2cWriteByteAndCheckAck((byte) reg);
340 | mpsse.i2cIdle();
341 | mpsse.i2cStart();
342 | mpsse.i2cWriteByteAndCheckAck(getAddressByte(address, true));
343 | if (length > 0) {
344 | mpsse.i2cReadBytes(length);
345 | mpsse.i2cReceiveImmediate();
346 | mpsse.bufferWrite();
347 | }
348 | dataRead(readBuffer, length);
349 | if (length > 0) {
350 | System.arraycopy(readBuffer, 0, data, 0, length);
351 | }
352 | mpsse.i2cStop();
353 | mpsse.bufferWrite();
354 | }
355 |
356 | private void readData(int address, byte[] data, int length) throws IOException {
357 | checkReadDataLength(length, data.length);
358 | mpsse.bufferClear();
359 | mpsse.i2cIdle();
360 | mpsse.i2cStart();
361 | mpsse.i2cWriteByteAndCheckAck(getAddressByte(address, true));
362 | if (length > 0) {
363 | mpsse.i2cReadBytes(length);
364 | mpsse.i2cReceiveImmediate();
365 | mpsse.bufferWrite();
366 | }
367 | dataRead(readBuffer, length);
368 | if (length > 0) {
369 | System.arraycopy(readBuffer, 0, data, 0, length);
370 | }
371 | mpsse.i2cStop();
372 | mpsse.bufferWrite();
373 | }
374 |
375 | private void writeData(int address, byte[] data, int length) throws IOException {
376 | checkDataLength(length, data.length);
377 | mpsse.bufferClear();
378 | mpsse.i2cIdle();
379 | mpsse.i2cStart();
380 | mpsse.i2cWriteByteAndCheckAck(getAddressByte(address, false));
381 | for (int i = 0; i < length; i++) {
382 | mpsse.i2cWriteByteAndCheckAck(data[i]);
383 | }
384 | mpsse.i2cStop();
385 | mpsse.bufferWrite();
386 | }
387 |
388 | /**
389 | * Write command to FT232H.
390 | *
391 | * @param cmd command to write
392 | * @param value command value
393 | * @throws IOException in case of command write error
394 | */
395 | private void commandWrite(int cmd, int value) throws IOException {
396 | controlTransfer(UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT,
397 | cmd, value, 1, null, 0);
398 | }
399 |
400 | private static long getMillis() {
401 | return System.nanoTime() / NANOS_IN_MS;
402 | }
403 |
404 | /**
405 | * Read data from FT232H to data buffer until an
406 | * expected number of bytes is returned.
407 | *
408 | * @param data data buffer to read data
409 | * @param length data length to read
410 | * @throws IOException in case of data read error or timeout
411 | */
412 | private void dataRead(byte[] data, int length) throws IOException {
413 | int read = 0;
414 | int offset = 0;
415 | long startTimestamp = getMillis();
416 | int timeoutRemain;
417 | while (true) {
418 | timeoutRemain = (int) (USB_TIMEOUT_MILLIS - (getMillis() - startTimestamp));
419 | if (timeoutRemain <= 0) {
420 | throw new IOException("Data read timeout");
421 | }
422 | int readOffset = offset + read;
423 | read += bulkRead(data, readOffset, data.length - readOffset, timeoutRemain);
424 | if (read >= READ_PACKET_HEADER_LENGTH) {
425 | int dataRead = read - READ_PACKET_HEADER_LENGTH;
426 | if (dataRead > 0) {
427 | // Cut out packet header
428 | System.arraycopy(data, offset + READ_PACKET_HEADER_LENGTH, data,
429 | offset, dataRead);
430 | }
431 | read = 0;
432 | offset += dataRead;
433 | if (offset >= length) {
434 | break;
435 | }
436 | }
437 | try {
438 | Thread.sleep(LATENCY_TIMER / 2);
439 | } catch (InterruptedException ignored) {
440 | }
441 | }
442 | }
443 |
444 | /**
445 | * Write data from data buffer to FT232H.
446 | *
447 | * @param data data buffer to write data
448 | * @param length data length to write
449 | * @throws IOException in case of data write error
450 | */
451 | protected void dataWrite(byte[] data, int length) throws IOException {
452 | bulkWrite(data, length, USB_TIMEOUT_MILLIS);
453 | }
454 |
455 | public static UsbDeviceIdentifier[] getSupportedUsbDeviceIdentifiers() {
456 | return new UsbDeviceIdentifier[] {
457 | new UsbDeviceIdentifier(0x403, 0x6014)
458 | };
459 | }
460 | }
461 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/github/ykc3/android/usbi2c/adapter/Ft260UsbI2cAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2025 Victor Antonovich
32 | * Data Sheet
33 | *
34 | * User Guide
35 | */
36 | public class Ft260UsbI2cAdapter extends HidUsbI2cAdapter {
37 | // Adapter name
38 | private static final String ADAPTER_NAME = "FT260";
39 |
40 | // FT260 report IDs
41 | private static final int REPORT_ID_CHIP_VERSION = 0xA0;
42 | private static final int REPORT_ID_SYSTEM_SETTINGS = 0xA1;
43 | private static final int REPORT_ID_I2C_STATUS = 0xC0;
44 | private static final int REPORT_ID_I2C_READ_REQUEST = 0xC2;
45 | private static final int REPORT_ID_I2C_DATA_MIN = 0xD0;
46 | private static final int REPORT_ID_I2C_DATA_MAX = 0xDE;
47 |
48 | // FT260 requests
49 | private static final int REQUEST_I2C_RESET = 0x20;
50 | private static final int REQUEST_I2C_SET_CLOCK_SPEED = 0x22;
51 |
52 | // FT260 report sizes (in bytes, including ReportID)
53 | private static final int REPORT_SIZE_CHIP_VERSION = 13;
54 | private static final int REPORT_SIZE_SYSTEM_STATUS = 25;
55 | private static final int REPORT_SIZE_I2C_STATUS = 5;
56 |
57 | // FT260 chip version report field offsets
58 | private static final int CHIP_VERSION_PART_NUMBER_MAJOR_OFFSET = 1;
59 | private static final int CHIP_VERSION_PART_NUMBER_MINOR_OFFSET = 2;
60 |
61 | // FT260 system status report field offsets
62 | private static final int SYSTEM_STATUS_CHIP_MODE_OFFSET = 1;
63 |
64 | // FT260 I2C status report field offsets
65 | private static final int I2C_STATUS_BUS_STATUS_OFFSET = 1;
66 |
67 | // FT260 I2C read request flags
68 | private static final int I2C_READ_REQUEST_FLAG_NONE = 0x00;
69 | private static final int I2C_READ_REQUEST_FLAG_START = 0x02;
70 | private static final int I2C_READ_REQUEST_FLAG_REPEATED_START = 0x03;
71 | private static final int I2C_READ_REQUEST_FLAG_STOP = 0x04;
72 | private static final int I2C_READ_REQUEST_FLAG_START_STOP = 0x06;
73 | private static final int I2C_READ_REQUEST_FLAG_START_STOP_REPEATED = 0x07;
74 |
75 | // FT260 max data transfer status reads while waiting for transfer completion
76 | private static final int CHECK_TRANSFER_STATUS_MAX_RETRIES = 10;
77 |
78 | // FT260 I2C status flags
79 | private static final int I2C_STATUS_FLAG_CONTROLLER_BUSY = 0x01;
80 | private static final int I2C_STATUS_FLAG_ERROR = 0x02;
81 |
82 | // FT260 min clock speed (60 kHz)
83 | private static final int MIN_CLOCK_SPEED = 60000;
84 | // FT260 max clock speed (3.4 MHz)
85 | private static final int MAX_CLOCK_SPEED = CLOCK_SPEED_HIGH;
86 |
87 | // FT260 max data transfer size
88 | private static final int MAX_DATA_TRANSFER_SIZE = 60;
89 |
90 | class Ft260UsbI2cDevice extends BaseUsbI2cDevice {
91 | Ft260UsbI2cDevice(int address) {
92 | super(address);
93 | }
94 |
95 | @Override
96 | protected void deviceReadReg(int reg, byte[] buffer, int length) throws IOException {
97 | readRegData(address, reg, buffer, length);
98 | }
99 |
100 | @Override
101 | protected void deviceRead(byte[] buffer, int length) throws IOException {
102 | readData(address, buffer, length);
103 | }
104 |
105 | @Override
106 | protected void deviceWrite(byte[] buffer, int length) throws IOException {
107 | writeData(address, buffer, length);
108 | }
109 | }
110 |
111 | public Ft260UsbI2cAdapter(UsbI2cManager manager, UsbDevice device) {
112 | super(manager, device);
113 | }
114 |
115 | @Override
116 | public String getName() {
117 | return ADAPTER_NAME;
118 | }
119 |
120 |
121 | @Override
122 | protected BaseUsbI2cDevice getDeviceImpl(int address) {
123 | return new Ft260UsbI2cDevice(address);
124 | }
125 |
126 | @Override
127 | protected void init(UsbDevice usbDevice) throws IOException {
128 | probe();
129 | super.init(usbDevice);
130 | }
131 |
132 | private void probe() throws IOException {
133 | // Check chip code
134 | getHidFeatureReport(REPORT_ID_CHIP_VERSION, buffer, REPORT_SIZE_CHIP_VERSION);
135 | if (buffer[CHIP_VERSION_PART_NUMBER_MAJOR_OFFSET] != 0x02
136 | || buffer[CHIP_VERSION_PART_NUMBER_MINOR_OFFSET] != 0x60) {
137 | throw new IOException(String.format("Unknown chip code: %02x%02x",
138 | buffer[CHIP_VERSION_PART_NUMBER_MAJOR_OFFSET],
139 | buffer[CHIP_VERSION_PART_NUMBER_MINOR_OFFSET]));
140 | }
141 | // Check I2C is enabled
142 | getHidFeatureReport(REPORT_ID_SYSTEM_SETTINGS, buffer, REPORT_SIZE_SYSTEM_STATUS);
143 | // I2C is enabled if DCNF0/DCNF1 pins are both set to 0 or DCNF0 is set to 1
144 | if (buffer[SYSTEM_STATUS_CHIP_MODE_OFFSET] != 0
145 | && (buffer[SYSTEM_STATUS_CHIP_MODE_OFFSET] & 0x01) == 0) {
146 | throw new IOException("I2C interface is not enabled by FT260 DCNF0/DCNF1 pins");
147 | }
148 | }
149 |
150 | @Override
151 | public boolean isClockSpeedSupported(int speed) {
152 | return (speed >= MIN_CLOCK_SPEED && speed <= MAX_CLOCK_SPEED);
153 | }
154 |
155 | @Override
156 | protected void configure() throws IOException {
157 | // Reset I2C master
158 | resetI2cMaster();
159 | // Set clock speed (in kHz)
160 | int clockSpeedKHz = getClockSpeed() / 1000;
161 | int i = 0;
162 | buffer[i++] = (byte) REPORT_ID_SYSTEM_SETTINGS;
163 | buffer[i++] = REQUEST_I2C_SET_CLOCK_SPEED;
164 | buffer[i++] = (byte) clockSpeedKHz;
165 | buffer[i++] = (byte) (clockSpeedKHz >> 8);
166 | setHidFeatureReport(buffer, i);
167 | }
168 |
169 | private void writeData(int address, byte[] data, int length) throws IOException {
170 | checkDataLength(length, data.length);
171 | // Write data by chunks up to MAX_DATA_TRANSFER_SIZE
172 | int sentBytes = 0;
173 | while (sentBytes < length) {
174 | int chunkLength = Math.min(length - sentBytes, MAX_DATA_TRANSFER_SIZE);
175 | int reportId = REPORT_ID_I2C_DATA_MIN + (chunkLength - 1) / 4;
176 | // Send write request
177 | int flag = I2C_READ_REQUEST_FLAG_NONE;
178 | if (sentBytes > 0) {
179 | if (sentBytes + chunkLength == length) {
180 | flag = I2C_READ_REQUEST_FLAG_STOP;
181 | }
182 | } else if (chunkLength < length) {
183 | flag = I2C_READ_REQUEST_FLAG_START;
184 | } else {
185 | flag = I2C_READ_REQUEST_FLAG_START_STOP;
186 | }
187 | int i = 0;
188 | buffer[i++] = (byte) reportId;
189 | buffer[i++] = (byte) address;
190 | buffer[i++] = (byte) flag;
191 | buffer[i++] = (byte) length;
192 | System.arraycopy(data, sentBytes, buffer, i, chunkLength);
193 | sendHidDataReport(buffer, i + chunkLength);
194 | sentBytes += chunkLength;
195 | // Check transfer status
196 | checkTransferStatus();
197 | }
198 | }
199 |
200 | private void readData(int address, byte[] data, int length) throws IOException {
201 | checkDataLength(length, data.length);
202 | // Send read request
203 | int i = 0;
204 | buffer[i++] = (byte) REPORT_ID_I2C_READ_REQUEST;
205 | buffer[i++] = (byte) address;
206 | buffer[i++] = (byte) I2C_READ_REQUEST_FLAG_START_STOP;
207 | buffer[i++] = (byte) length;
208 | buffer[i++] = (byte) (length >> 8);
209 | sendHidDataReport(buffer, i);
210 | // Read response
211 | readDataFully(data, length);
212 | // Check transfer status
213 | checkTransferStatus();
214 | }
215 |
216 | private void readRegData(int address, int reg, byte[] data, int length) throws IOException {
217 | checkDataLength(length, data.length);
218 | // Send register address write request
219 | int i = 0;
220 | buffer[i++] = (byte) REPORT_ID_I2C_DATA_MIN;
221 | buffer[i++] = (byte) address;
222 | buffer[i++] = (byte) I2C_READ_REQUEST_FLAG_START;
223 | buffer[i++] = 0x01; // number of bytes in target address (register ID)
224 | buffer[i++] = (byte) reg;
225 | sendHidDataReport(buffer, i);
226 | // Check register address write transfer status
227 | checkTransferStatus();
228 | // Send data read request
229 | i = 0;
230 | buffer[i++] = (byte) REPORT_ID_I2C_READ_REQUEST;
231 | buffer[i++] = (byte) address;
232 | buffer[i++] = (byte) I2C_READ_REQUEST_FLAG_START_STOP_REPEATED;
233 | buffer[i++] = (byte) length;
234 | buffer[i++] = (byte) (length >> 8);
235 | sendHidDataReport(buffer, i);
236 | // Register data read response
237 | readDataFully(data, length);
238 | // Check data read transfer status
239 | checkTransferStatus();
240 | }
241 |
242 | private void readDataFully(byte[] data, int length) throws IOException {
243 | int readLength = 0;
244 |
245 | while (readLength < length) {
246 | getHidDataReport(buffer, USB_TIMEOUT_MILLIS);
247 |
248 | int reportId = buffer[0] & 0xff;
249 | if (reportId < REPORT_ID_I2C_DATA_MIN || reportId > REPORT_ID_I2C_DATA_MAX) {
250 | throw new IOException(String.format("Unexpected data read report ID: 0x%02x",
251 | reportId));
252 | }
253 |
254 | int bufferLength = buffer[1] & 0xff;
255 | if (bufferLength > length - readLength) {
256 | throw new IOException(String.format("Too long data to read: " +
257 | "%d byte(s), expected: %d byte(s)", bufferLength, length - readLength));
258 | }
259 |
260 | System.arraycopy(buffer, 2, data, readLength, bufferLength);
261 |
262 | readLength += bufferLength;
263 | }
264 | }
265 |
266 | private void checkTransferStatus() throws IOException {
267 | int tryNum = 1;
268 |
269 | while (tryNum++ <= CHECK_TRANSFER_STATUS_MAX_RETRIES) {
270 | int status = getTransferStatus();
271 |
272 | if ((status & I2C_STATUS_FLAG_CONTROLLER_BUSY) != 0) {
273 | continue;
274 | }
275 |
276 | if ((status & I2C_STATUS_FLAG_ERROR) != 0) {
277 | throw new IOException(String.format("Transfer error, status: 0x%02x", status));
278 | }
279 |
280 | // Transfer is complete and no error was occurred
281 | return;
282 | }
283 |
284 | // Can't check transfer status because of timeout
285 | resetI2cMaster();
286 | throw new IOException("Can't check transfer status: reties limit was reached");
287 | }
288 |
289 | private int getTransferStatus() throws IOException {
290 | getHidFeatureReport(REPORT_ID_I2C_STATUS, buffer, REPORT_SIZE_I2C_STATUS);
291 | return buffer[I2C_STATUS_BUS_STATUS_OFFSET];
292 | }
293 |
294 | private void resetI2cMaster() throws IOException {
295 | int i = 0;
296 | buffer[i++] = (byte) REPORT_ID_SYSTEM_SETTINGS;
297 | buffer[i++] = REQUEST_I2C_RESET;
298 | setHidFeatureReport(buffer, i);
299 | }
300 |
301 | public static UsbDeviceIdentifier[] getSupportedUsbDeviceIdentifiers() {
302 | return new UsbDeviceIdentifier[] {
303 | new UsbDeviceIdentifier(0x403, 0x6030)
304 | };
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/github/ykc3/android/usbi2c/adapter/HidUsbI2cAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2025 Victor Antonovich