This is a summary of what I did - for those that are searching, and I spent literally days on this. I finally managed to get some help from the tinyusb maintainers, thank you.
What I want to do...
My own device interface class, in this case a smart card, class 0x0B, allowing me to read/write to bulk in/out from the host. Smart Card (CCID) uses simple message to talk to a smart card.
The key points in making it work...
This link was a good start, but missed a few details I have found out.
- idf.py add-dependency tinyusb (not, not esp_tinyusb as we are working at a low level)
- Add "tinyusb" and "usb" to COMPONENT_REQUIRES in main/CMakeLists.txt
- Copy a suitable tusb_config.h to main (I used the webusb_serial one). You'll need to edit this.
- Add suggested extra lines. I put my files directly in main, so I added...
# espressif__tinyusb should match the current tinyusb dependency name
idf_component_get_property(tusb_lib espressif__tinyusb COMPONENT_LIB)
target_include_directories(${tusb_lib} PUBLIC "${COMPONENT_DIR}/")
Includes
#include "tusb.h"
#include "esp_private/usb_phy.h"
#include "device/usbd_pvt.h"
This should get you compiling, but you will find it expects a few functions to be provided...
tud_descriptor_device_cb
This returns a device descriptor - this is actually quite easy to construct with something like this and return a pointer to it (needs to stay in memory, so declaring statically or even const).
tusb_desc_device_t usb_device = {
.bLength = sizeof (usb_device),
.bDescriptorType = TUSB_DESC_DEVICE,
.bMaxPacketSize0 = 64,
.idVendor = 0x1234,
.idProduct = 0x5678,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 1,
};
Note the .bNumConfigurations = 1 so only one configuration.
tud_descriptor_configuration_cb
This needs to return the configuration, which includes the interface and endpoints. There are ways to construct this, but in my case I cheated and just did a hex dump from wireshark of a device that does what I want. Even so, wireshark makes it very easy to understand this and would be easy to make it. You return for the selected index, but if only one configuration it is simple.
tud_descriptor_string_cb
This is fun, as what you return is a uint16_t. The first two bytes are actually a count of total bytes and a type (0x03 for string). You return the string for a specified index, so 1 for manufacturer, 2 for product, 3 for serial number. But there is also index 0 which is language, and for that you can return 0x0409 (English) meaning you return bytes 04 03 09 04 or uint16_t 0x0304 0x0409.
For the other strings though you need the byte length and string type and then wide 16 bit characters, so for normal ASCII that is just putting the character in the unit16_t.
Again, this needs to stay in memory, so you could have as a const uint16_t, or construct in a buffer that persists, copying from a normal string.
Your own TinyUSB device driver.
Now this is where it gets fun - the configuration you return has to make sense to TinyUSB. It can have a number of devices configured from the tusb_config.h, e.g. #define CFG_TUD_HUD 1 and that installs the drivers for HID. But the driver looks for a HID class in the configuration. If I want smart card (0x0B) to be recognised I need a driver. This is where the tinyusb team helped a lot.
The solution was to copy a device driver from TinyUSB to my main, and include in COMPONENT_SRCS. I copied vendor_device.c to ccid_device.c and changed all vendor/VENDOR to ccid/CCID. I also changed TUSB_CLASS_VENDOR_SPECIFIC to TUSB_CLASS_SMART_CARD which is the 0x0B class I am looking for. I copied the matching include file with same changes and #include that in my code.
This then needs to be explained to TinyUSB. You do this by creating a driver list and function...
usbd_class_driver_t const ccid_driver = { #if CFG_TUSB_DEBUG >= 2 .name = "CCID driver", #endif .init = ccid_init, .reset = ccid_reset, .open = ccid_open, .control_xfer_cb = tud_vendor_control_xfer_cb,
.xfer_cb = ccid_xfer_cb, .sof = NULL }; usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* driver_count) { *driver_count = 1; return &ccid_driver; }
This then allowed me to use my new driver.
Vendor driver (now CCID driver) not quite working.
Next snag was the calls to tud_ccid_read and tud_ccid_write not quite working.
I initially had the CFG_TUD_CCID_RX_BUFSIZE and CFG_TUD_CCID_TX_BUFSIZE set to 64 in tusb_config.h. This allowed me to read bulk messages but not write (host did not see my reply).
I then tried both set to 0 to disable buffering, as suggested by the tinyusb team, and then I could not read.
Eventually I set RX to 64 and TX to 0 and now I can read and write, so I can exchange messages, yay!
Next steps
Next is to code the actual CCID protocol - which I may put in the driver to allow exchange of whole CCID messages rather than the USB packets.
No comments:
Post a Comment
Comments are moderated purely to filter out obvious spam, but it means they may not show immediately.