I2C Pico USB Adapter: Part 2 - USB Communication
The USB standard is complex. Even having studied it hard for my book on the RP2040, I admit I was sometimes confused. This particular application involves some functions of the tinyusb library that are not much discussed in the documentation.
USB Control Transfers, Taken from Figure 8-37 of the USB 2.0 specifications |
Let's start from the beginning. USB devices are grouped into classes. The I2C adapter is in the vendor class, meaning it does not follow a standard set of messages (like HID or serial devices). There are a few ways USB devices can transfer data, the I2C adapter uses control transfers. These transfers have a very limited payload, but are easier to implement (that makes a lot of sense when you are bit-banging the protocol in a 12 MHz 8-bit microcontroller).
The first thing needed to implement a USB device is to create a handful of descriptors. These are structures that inform the PC what the device is, what it does, and how to communicate with it. Two important pieces of information are the vendor and product IDs; I used the same ones as the original i2c-tiny-usb, so I can use the same drivers (note that you should not do this if you intend to manufacture and distribute the adapter). As said, the class of the device is vendor, it has only one interface with the two standard endpoints (one for input and the other for output).
Messages sent through control transfers have a fixed format but the interpretation of the fields is up to the device. In this case, one of the fields contains a command:
- Echo sends back information in the received message
- Get_Func asks for what capabilities our implementation support
- Set_Delay defines the clock frequency for the I2C bus (I am ignoring this for now)
- I2C_IO transfers data to or from an I2C device. Flags in this command control the generation of START and END conditions (more on this in the next part)
- Status sends back the result of the previous command
In the USB protocol, control transfers have three stages: SETUP, DATA (optional), and STATUS. One confusing thing in tinyusb is when your code is called and what you should do at each stage.
The routine you need to code is tud_vendor_control_xfer_cb(); one of its parameters in the stage:
- SETUP means that the command has been received and you should set up the answer you will send. If the command is I2C_IO, send the I2C address to the I2C device and check for the device acknowledgment. If the command is an I2C read, perform the reading and pass the result back to tinyusb for transmission.
- DATA means that the data transfer is done. If the command was an I2C write, now you can get the data to write from tinyusb (it took me some time to realize this).
- STATUS means the transfer is completed, nothing needs to be done.
You can see the full code at github: https://github.com/dquadros/I2C-Pico-USB.
Comments
Post a Comment