adafruit / Adafruit_TinyUSB_Arduino

Arduino library for TinyUSB
MIT License
465 stars 120 forks source link

USB I2C bridge example #252

Closed ita1024 closed 1 year ago

ita1024 commented 1 year ago

The snippets provided under the examples folder show a limited amount of supported devices. Would not it be amazing to show how to extend support to new USB gadgets?

I am not sure if this is the correct way (suggestions welcome), but it took me some time to create an i2c bridge using an Adafruit Trinket M0. This example shows in particular how to use "TinyUSBDevice.setID"; the device is autodetected on Linux using the i2c_tiny_usb module and is ready to use:

#include <Wire.h>
#include <Adafruit_TinyUSB.h>

#define ACK 1
#define NACK 2

class I2CInterface : public Adafruit_USBD_Interface {
public:
  I2CInterface(void);
  uint16_t getInterfaceDescriptor(uint8_t itfnum, uint8_t* buf, uint16_t bufsize) {
    uint8_t desc[] = { TUD_VENDOR_DESCRIPTOR(itfnum, 0, 0x00, 0x80, 64) };
    uint16_t const len = sizeof(desc);
    if (buf) {
      if (bufsize < len) {
        return 0;
      }
      memcpy(buf, desc, len);
    }
    return len;
  }
  bool begin(void);
};

I2CInterface* g_i2cinterface = NULL;

I2CInterface::I2CInterface() {
  setStringDescriptor("I2C interface");
}

bool I2CInterface::begin(void) {
  g_i2cinterface = this;
  return TinyUSBDevice.addInterface(*this);
}

static uint32_t i2c_func = 0x8eff0001;
static uint8_t i2c_state = 0;
static uint8_t i2c_buf[800];

bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) {
  if (!g_i2cinterface) {
    return false;
  }
  if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_VENDOR) {
    if (stage == CONTROL_STAGE_SETUP) {
      switch (request->bRequest) {
        case 0:
          // echo
          return tud_control_xfer(rhport, request, (void*)&request->wValue, sizeof(request->wValue));
          break;
        case 1:
          // capabilities
          return tud_control_xfer(rhport, request, (void*)&i2c_func, sizeof(i2c_func));
          break;
        case 2:
          if (request->wValue == 0) {
            Wire.setClock(115200);
          } else {
            int baudrate = 1000000 / request->wValue;
            if (baudrate > 400000) baudrate = 400000;
            Wire.setClock(baudrate);
          }
          return tud_control_status(rhport, request);
          break;
        case 3:
          return tud_control_xfer(rhport, request, (void*)&i2c_state, sizeof(i2c_state));
          break;
        case 4:
        case 5:
        case 6:
        case 7:
          {
            uint16_t address = request->wIndex;
            int count = Wire.requestFrom(address, request->wLength);
            if (count != request->wLength) {
              i2c_state = NACK;
            } else {
              uint32_t pos = 0;
              while (Wire.available()) {
                char cval = Wire.read();
                i2c_buf[pos] = cval;
                pos++;
              }
              i2c_state = ACK;
            }
            tud_control_xfer(rhport, request, i2c_buf, request->wLength);
          }
          return true;
        default:
          return true;
      }
    } else if (stage == CONTROL_STAGE_DATA) {
      switch (request->bRequest) {
        case 4:
        case 5:
        case 6:
        case 7:
          {
            bool stop = request->bRequest > 5;
            uint16_t address = request->wIndex;
            if (!(request->wValue == 1)) {
              Wire.beginTransmission(address);
              Wire.write(i2c_buf, request->wLength);
              i2c_state = Wire.endTransmission(stop);
            }
            tud_control_xfer(rhport, request, i2c_buf, request->wLength);
          }
          return true;
        default:
          return true;
      }
    } else {
      return true;
    }
  }
  return false;
}

I2CInterface i2cinterface;
void setup() {
  // needed to identify as a device for i2c_tiny_usb
  TinyUSBDevice.setID(0x1c40, 0x0534);
  Wire.begin();
  i2cinterface.begin();
}

void loop() {
}

The tud_control_xfer function shares the buffer implicitly from the SETUP to the DATA stage. Let me know if there is a better way, and feel free to adapt the snippet above to add a proper example to the documentation!

hathach commented 1 year ago

this is a great idea, give me a bit time, I will do some test, and add an example based on your code and maybe add a helper/class driver to tinyusb core for this as well. That would be useful for others

hathach commented 1 year ago

Thank you for your suggestion, I have made an example and make an seperated WireUSB class to make it easier to re-use by other. #264 includes other fixes and get it working flawless with i2cdetect

$ i2cdetect -l
i2c-8   i2c         i2c-tiny-usb at bus 003 device 041  I2C adapter
$ i2cdetect -y 8
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- 39 -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --    
ita1024 commented 1 year ago

254 looks really good! I will try it as soon as possible.