Skip to content

Commit

Permalink
Add bus clock speed control methods to UsbI2cAdapter
Browse files Browse the repository at this point in the history
  • Loading branch information
3cky committed May 9, 2021
1 parent 6d9b716 commit 24ef9f7
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ static class ScanI2cDevicesTask extends AsyncTask<UsbDevice, Integer, Integer> {
@Override
protected Integer doInBackground(UsbDevice... usbDevices) {
try (UsbI2cAdapter usbI2cAdapter = fragment.usbI2cManager.getAdapter(usbDevices[0])) {
usbI2cAdapter.setClockSpeed(UsbI2cAdapter.CLOCK_SPEED_STANDARD);
usbI2cAdapter.open();
byte[] buf = new byte[1];
for (int i2cAddress = MIN_I2C_ADDRESS; i2cAddress <= MAX_I2C_ADDRESS; i2cAddress++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@
* I2C adapter connected to USB bus.
*/
public interface UsbI2cAdapter extends AutoCloseable {
/** Standard clock speed (100 kbit/s) */
int CLOCK_SPEED_STANDARD = 100000;
/** Fast clock speed (400 kbit/s) */
int CLOCK_SPEED_FAST = 400000;
/** Fast plus clock speed (1 Mbit/s) */
int CLOCK_SPEED_FAST_PLUS = 1000000;
/** High clock speed (3.4 Mbit/s) */
int CLOCK_SPEED_HIGH = 3400000;

/**
* Get I2C adapter identifier string.
*
Expand All @@ -40,6 +49,14 @@ public interface UsbI2cAdapter extends AutoCloseable {
*/
UsbDevice getUsbDevice();

/**
* Open I2C adapter for communicating to connected I2C devices.
*
* @throws IOException in case of I/O error
* @throws IllegalStateException if I2C adapter is already opened
*/
void open() throws IOException;

/**
* Get reference {@link UsbI2cDevice} connected to this I2C adapter.
*
Expand All @@ -50,10 +67,22 @@ public interface UsbI2cAdapter extends AutoCloseable {
UsbI2cDevice getDevice(int address);

/**
* Open I2C adapter for communicating to connected I2C devices.
* Check I2C bus clock speed is supported by adapter.
* UsbI2cAdapter.SPEED_STANDARD is guaranteed to be supported by all adapters.
*
* @param speed I2C bus clock speed value to check (in bit/s)
* @return true if speed is supported by I2C adapter, false if not supported
* @since 1.2
*/
boolean isClockSpeedSupported(int speed);

/**
* Set I2C bus clock speed. Default is UsbI2cAdapter.SPEED_STANDARD.
*
* @param speed I2C bus clock speed value to set (in bit/s)
* @throws IllegalArgumentException if this I2C bus clock speed is not supported by adapter
* @throws IOException in case of I/O error
* @throws IllegalStateException if I2C adapter is already opened
* @since 1.2
*/
void open() throws IOException;
void setClockSpeed(int speed) throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ abstract class BaseUsbI2cAdapter implements UsbI2cAdapter {

protected final ReentrantLock accessLock = new ReentrantLock();

protected int clockSpeed = CLOCK_SPEED_STANDARD;

protected abstract class BaseUsbI2cDevice implements UsbI2cDevice {
final int address;

Expand Down Expand Up @@ -165,9 +167,19 @@ public String getId() {
return usbDevice.getDeviceName();
}

protected boolean isOpened() {
return (usbDeviceConnection != null);
}

protected void checkOpened() throws IllegalStateException {
if (!isOpened()) {
throw new IllegalStateException("Adapter is not opened or closed");
}
}

@Override
public void open() throws IOException {
if (usbDeviceConnection != null) {
if (isOpened()) {
throw new IllegalStateException("Adapter already opened");
}

Expand All @@ -183,10 +195,10 @@ public void open() throws IOException {
}
}

open(usbDevice);
init(usbDevice);
}

protected void open(UsbDevice usbDevice) throws IOException {
protected void init(UsbDevice usbDevice) throws IOException {
// Do nothing by default
}

Expand All @@ -211,22 +223,51 @@ protected void close(UsbDevice usbDevice) throws IOException {

@Override
public UsbI2cDevice getDevice(int address) {
if (usbDeviceConnection == null) {
throw new IllegalStateException("Adapter closed");
}

checkOpened();
return getDeviceImpl(address);
}

protected abstract BaseUsbI2cDevice getDeviceImpl(int address);

@Override
public boolean isClockSpeedSupported(int speed) {
return (speed == CLOCK_SPEED_STANDARD);
}

protected int getClockSpeed() {
return clockSpeed;
}

@Override
public void setClockSpeed(int speed) throws IOException {
if (!isClockSpeedSupported(speed)) {
throw new IllegalArgumentException("Clock speed is not supported: " + speed);
}

this.clockSpeed = speed;

if (isOpened()) {
try {
accessLock.lock();
configure();
} finally {
accessLock.unlock();
}
}
}

protected void configure() throws IOException {
// Do nothing by default
}

@Override
public UsbDevice getUsbDevice() {
return usbDevice;
}

final void controlTransfer(int requestType, int request, int value,
int index, byte[] data, int length) throws IOException {
checkOpened();
int result = usbDeviceConnection.controlTransfer(requestType, request, value,
index, data, length, USB_TIMEOUT_MILLIS);
if (result != length) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;

import com.github.ykc3.android.usbi2c.UsbI2cDevice;
import com.github.ykc3.android.usbi2c.UsbI2cManager;

import static com.github.ykc3.android.usbi2c.UsbI2cManager.UsbDeviceIdentifier;
Expand Down Expand Up @@ -90,7 +89,7 @@ protected Ch341UsbI2cDevice getDeviceImpl(int address) {
}

@Override
protected void open(UsbDevice usbDevice) throws IOException {
protected void init(UsbDevice usbDevice) throws IOException {
if (usbDevice.getInterfaceCount() == 0) {
throw new IOException("No interfaces found for device: " + usbDevice);
}
Expand All @@ -115,13 +114,32 @@ protected void open(UsbDevice usbDevice) throws IOException {
throw new IOException("No read or write bulk endpoint found for device: " + usbDevice);
}

setupAdapter();
configure();
}

private void setupAdapter() throws IOException {
// Set I2C bus speed
protected int getClockSpeedConstant(int speed) {
switch (speed) {
case 20000:
return CH341_I2C_LOW_SPEED;
case CLOCK_SPEED_STANDARD:
return CH341_I2C_STANDARD_SPEED;
case CLOCK_SPEED_FAST:
return CH341_I2C_FAST_SPEED;
case 750000:
return CH341_I2C_HIGH_SPEED;
}

return -1;
}

@Override
public boolean isClockSpeedSupported(int speed) {
return (getClockSpeedConstant(speed) >= 0);
}

protected void configure() throws IOException {
writeBuffer[0] = (byte) CH341_CMD_I2C_STREAM;
writeBuffer[1] = CH341_CMD_I2C_STM_SET | CH341_I2C_STANDARD_SPEED;
writeBuffer[1] = (byte) (CH341_CMD_I2C_STM_SET | getClockSpeedConstant(getClockSpeed()));
writeBuffer[2] = CH341_CMD_I2C_STM_END;
writeBulkData(writeBuffer, 3);
}
Expand Down Expand Up @@ -237,6 +255,7 @@ private int transferBulkData(byte[] writeData, int writeLength,
* @throws IOException in case of data read error or timeout
*/
private int readBulkData(byte[] data, int length) throws IOException {
checkOpened();
int res = usbDeviceConnection.bulkTransfer(usbReadEndpoint, data,
length, BaseUsbI2cAdapter.USB_TIMEOUT_MILLIS);
if (res < 0) {
Expand All @@ -253,6 +272,7 @@ private int readBulkData(byte[] data, int length) throws IOException {
* @throws IOException in case of data write error
*/
private void writeBulkData(byte[] data, int length) throws IOException {
checkOpened();
int res = usbDeviceConnection.bulkTransfer(usbWriteEndpoint, data, length,
BaseUsbI2cAdapter.USB_TIMEOUT_MILLIS);
if (res < 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;

import com.github.ykc3.android.usbi2c.UsbI2cDevice;
import com.github.ykc3.android.usbi2c.UsbI2cManager;

import java.io.IOException;
Expand Down Expand Up @@ -68,6 +67,11 @@ public class Cp2112UsbI2cAdapter extends BaseUsbI2cAdapter {
// CP2112 SMBus configuration size
private static final int SMBUS_CONFIG_SIZE = 13;

// CP2112 SMBus configuration: Clock Speed
private static final int SMBUS_CONFIG_CLOCK_SPEED_OFFSET = 1;
// CP2112 SMBus configuration: Retry Time
private static final int SMBUS_CONFIG_RETRY_TIME_OFFSET = 12;

// CP2112 max I2C data write length (single write transfer)
private static final int MAX_DATA_WRITE_LENGTH = 61;

Expand All @@ -85,6 +89,11 @@ public class Cp2112UsbI2cAdapter extends BaseUsbI2cAdapter {
private static final int TRANSFER_STATUS_COMPLETE = 0x02;
private static final int TRANSFER_STATUS_ERROR = 0x03;

// CP2112 min clock speed
private static final int MIN_CLOCK_SPEED = 10000;
// CP2112 max clock speed
private static final int MAX_CLOCK_SPEED = CLOCK_SPEED_HIGH;

private UsbEndpoint usbReadEndpoint;
private UsbEndpoint usbWriteEndpoint;

Expand Down Expand Up @@ -119,7 +128,7 @@ public Cp2112UsbI2cDevice getDeviceImpl(int address) {
}

@Override
protected void open(UsbDevice usbDevice) throws IOException {
protected void init(UsbDevice usbDevice) throws IOException {
if (usbDevice.getInterfaceCount() == 0) {
throw new IOException("No interfaces found for device: " + usbDevice);
}
Expand All @@ -144,19 +153,35 @@ protected void open(UsbDevice usbDevice) throws IOException {
throw new IOException("No read or write HID endpoint found for device: " + usbDevice);
}

setupAdapter();
}
configure();

private void setupAdapter() throws IOException {
getHidFeatureReport(REPORT_ID_SMBUS_CONFIG, buffer, SMBUS_CONFIG_SIZE + 1); // reserve one byte for Report ID
// Strip first byte (Report ID)
System.arraycopy(buffer, 1, buffer, 0, SMBUS_CONFIG_SIZE);
buffer[SMBUS_CONFIG_SIZE - 1] = 0x01; // Retry Time (number of retries, default 0 - no limit)
sendHidFeatureReport(REPORT_ID_SMBUS_CONFIG, buffer, SMBUS_CONFIG_SIZE);
// Drain all stale data reports
drainPendingDataReports();
}

protected void configure() throws IOException {
// Get current config
getHidFeatureReport(REPORT_ID_SMBUS_CONFIG, buffer, SMBUS_CONFIG_SIZE + 1); // reserve one byte for Report ID

// Clock Speed (in Hertz, default 0x000186A0 - 100 kHz)
int clockSpeed = getClockSpeed();
buffer[SMBUS_CONFIG_CLOCK_SPEED_OFFSET] = (byte) (clockSpeed >> 24);
buffer[SMBUS_CONFIG_CLOCK_SPEED_OFFSET + 1] = (byte) (clockSpeed >> 16);
buffer[SMBUS_CONFIG_CLOCK_SPEED_OFFSET + 2] = (byte) (clockSpeed >> 8);
buffer[SMBUS_CONFIG_CLOCK_SPEED_OFFSET + 3] = (byte) clockSpeed;

// Retry Time (number of retries, default 0 - no limit)
buffer[SMBUS_CONFIG_RETRY_TIME_OFFSET] = 0x00;
buffer[SMBUS_CONFIG_RETRY_TIME_OFFSET + 1] = 0x01;

sendHidFeatureReport(REPORT_ID_SMBUS_CONFIG, buffer, SMBUS_CONFIG_SIZE + 1);
}

@Override
public boolean isClockSpeedSupported(int speed) {
return (speed >= MIN_CLOCK_SPEED && speed <= MAX_CLOCK_SPEED);
}

private void checkWriteDataLength(int length) {
checkDataLength(length, MAX_DATA_WRITE_LENGTH);
}
Expand Down Expand Up @@ -343,6 +368,7 @@ private void sendHidFeatureReport(int reportId, byte[] data, int length) throws
* @throws IOException in case of data report read error or timeout
*/
private int getHidDataReport(byte[] data, int timeout) throws IOException {
checkOpened();
int res = usbDeviceConnection.bulkTransfer(usbReadEndpoint, data, data.length, timeout);
if (res < 0) {
throw new IOException("Get HID data report error, result: " + res);
Expand All @@ -359,6 +385,7 @@ private int getHidDataReport(byte[] data, int timeout) throws IOException {
* @throws IOException in case of data report send error
*/
private void sendHidDataReport(byte[] data, int length, int timeout) throws IOException {
checkOpened();
int res = usbDeviceConnection.bulkTransfer(usbWriteEndpoint, data, length, timeout);
if (res < 0) {
throw new IOException("Send HID data report error, result: " + res);
Expand Down

0 comments on commit 24ef9f7

Please sign in to comment.