Hi, I started recently to port an Arduino Due based bulk USB interface to TeensyDuino for Teensy 4.1
The main difference seems that Arduino supported the PluggableUSB class that allows to extend the USB device with interfaces and endpoints and its associated required functions for enumerations as such :
While for teensyduino, it's all inside the framework cores source. (at least for now?)
So I tried to extend it with a "vendor bulk" interface on top of Serial, and it seems ok from the enumeration standpoint, I still will need to benchmark it and troubleshoot using a tester and host based libusb code.
I think it could be nice to extend the framework for a CDC ACM serial (status EP and IN/OUT endpoint) + Vendor Bulk (EP IN/OUT endpoints, 512 MxPacketSize, High speed USB).
That would provide a reasonable general use case for a large number of data transfer oriented projects, where the CDC ACM is then repurposed for telemetry, control, and connection control (such as linestate etc), and the vendor interface and its endpoints for fast data transfer (as it allows to get rid of all host serial associated clutter such as line discipline, special character management, cdc_acm driver binding, internal buffering "black box" management, which can be cumbersome, at least on linux)
The extension was done like this :
The only trouble i am having is making a c++ usb_vendor_class object available for sketches as a Vendor instance, as usb_inst.cpp refuses to compile, so i commented it out :
I get :
I probably forgot some code patching somewhere ?
And finally some little issue in enumeration :
When building with the vanilla USB_SERIAL compile flag this gives this enumeration on linux (see file usb_serial.png)
When building with the USB_VENDOR compile flag this gives this enumeration on linux (see file usb_vendor.png)
There is a mysterious interface that gets overwritten with the vendor interface i just added.
Sub=6a, Prot=c7.
What is that interface for ?
I will probably have to figure the interface ID and interface num defines and endpoint defines so as not overwrite it, unless someone knowledgeable as an idea.
ps : i am using 96 bytes tx maxkpacket sizes and 64 bytes rx maxpacketsize in the screenshots for my use case instead of the more general 512 byte size I provided in the code sample of this post.[/CODE]
The main difference seems that Arduino supported the PluggableUSB class that allows to extend the USB device with interfaces and endpoints and its associated required functions for enumerations as such :
Code:
class DataBulk_: public PluggableUSBModule {
public:
DataBulk_();
//uint32_t available();
uint32_t write(void *buf, size_t len);
protected:
virtual bool setup(USBSetup &setup) override;
virtual int getInterface(uint8_t *interfaceCount) override;
virtual int getDescriptor(USBSetup &setup) override;
virtual uint8_t getShortName(char *name) override;
private:
uint32_t epType[2];
};
While for teensyduino, it's all inside the framework cores source. (at least for now?)
So I tried to extend it with a "vendor bulk" interface on top of Serial, and it seems ok from the enumeration standpoint, I still will need to benchmark it and troubleshoot using a tester and host based libusb code.
I think it could be nice to extend the framework for a CDC ACM serial (status EP and IN/OUT endpoint) + Vendor Bulk (EP IN/OUT endpoints, 512 MxPacketSize, High speed USB).
That would provide a reasonable general use case for a large number of data transfer oriented projects, where the CDC ACM is then repurposed for telemetry, control, and connection control (such as linestate etc), and the vendor interface and its endpoints for fast data transfer (as it allows to get rid of all host serial associated clutter such as line discipline, special character management, cdc_acm driver binding, internal buffering "black box" management, which can be cumbersome, at least on linux)
The extension was done like this :
Code:
In usb.c :
37d36
< #include "usb_vendor.h"
489,491d487
< #endif
< #if defined(VENDOR_INTERFACE)
< usb_vendor_configure();
Code:
In usb_desc.c :
87,100d86
< // OPTIONAL: uncommenting the following comment block makes the device entirely transparent to cdc_acm
< // driver on linux, and would require manual binding of the kernel driver.
< /*
< #ifdef VENDOR_DESCRIPTOR
< #ifndef DEVICE_CLASS
< #define DEVICE_CLASS 0xFF
< #endif
< #ifndef DEVICE_SUBCLASS
< #define DEVICE_SUBCLASS 0x00
< #endif
< #ifndef DEVICE_PROTOCOL
< #define DEVICE_PROTOCOL 0x00
< #endif
< #endif
< */
<// END OPTIONAL.
<
601,608c587
< #define VENDOR_INTERFACE_DESC_POS MIDI_INTERFACE_DESC_POS+MIDI_INTERFACE_DESC_SIZE
< #ifdef VENDOR_INTERFACE
< #define VENDOR_INTERFACE_DESC_SIZE 9+7+7
< #else
< #define VENDOR_INTERFACE_DESC_SIZE 0
< #endif
<
< #define KEYBOARD_INTERFACE_DESC_POS VENDOR_INTERFACE_DESC_POS+VENDOR_INTERFACE_DESC_SIZE
---
> #define KEYBOARD_INTERFACE_DESC_POS MIDI_INTERFACE_DESC_POS+MIDI_INTERFACE_DESC_SIZE
1172,1199d1150
<
< #ifdef VENDOR_INTERFACE
< // configuration for 480 Mbit/sec speed
< // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12
< 9, // bLength
< 4, // bDescriptorType
< VENDOR_INTERFACE, // bInterfaceNumber
< 0, // bAlternateSetting
< 2, // bNumEndpoints
< 0xFF, // bInterfaceClass (0xFF = vendor)
< 0x00, // bInterfaceSubClass
< 0x00, // bInterfaceProtocol
< 0, // iInterface
< // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
< 7, // bLength
< 5, // bDescriptorType
< VENDOR_RX_ENDPOINT | 0x80, // bEndpointAddress
< 0x02, //USB_ENDPOINT_TYPE_BULK // bmAttributes (0x02=bulk)
< LSB(VENDOR_RX_SIZE),MSB(VENDOR_RX_SIZE),// wMaxPacketSize
< 0, // bInterval
< // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
< 7, // bLength
< 5, // bDescriptorType
< VENDOR_TX_ENDPOINT, // bEndpointAddress
< 0x02, //USB_ENDPOINT_TYPE_BULK // bmAttributes (0x02=bulk)
< LSB(VENDOR_TX_SIZE),MSB(VENDOR_TX_SIZE),// wMaxPacketSize
< 0, // bInterval
< #endif // VENDOR INTERFACE
Code:
in usb_desc.h
587,622d586
< #elif defined(USB_VENDOR)
<
< #define VENDOR_ID 0x16C0
< #define PRODUCT_ID 0x0486
< #define MANUFACTURER_NAME {'V','E','N','D','O','R'}
< #define MANUFACTURER_NAME_LEN 6
< #define PRODUCT_NAME {'D','E','V','I','C','E'}
< #define PRODUCT_NAME_LEN 6
< #define EP0_SIZE 64
< #define NUM_ENDPOINTS 4
< #define NUM_INTERFACE 3
< #define VENDOR_INTERFACE 2
< #define VENDOR_TX_ENDPOINT 6
< #define VENDOR_TX_SIZE 512
< #define VENDOR_TX_INTERVAL 1 // TODO: is this ok for 480 Mbit speed
< #define VENDOR_RX_ENDPOINT 5
< #define VENDOR_RX_SIZE 512
< #define VENDOR_RX_INTERVAL 1 // TODO: is this ok for 480 Mbit speed
< #define VENDOR_DESCRIPTOR 1 // Vendor-specific device descriptor
< #define CDC_STATUS_INTERFACE 0
< #define CDC_DATA_INTERFACE 1
< #define CDC_ACM_ENDPOINT 2
< #define CDC_RX_ENDPOINT 3
< #define CDC_TX_ENDPOINT 4
< #define CDC_ACM_SIZE 16
< #define CDC_RX_SIZE_480 512
< #define CDC_TX_SIZE_480 512
< #define CDC_RX_SIZE_12 64
< #define CDC_TX_SIZE_12 64
<
< #define ENDPOINT2_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT // EP0?
< #define ENDPOINT3_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_UNUSED // CDC ACM RX EP
< #define ENDPOINT4_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_BULK // CDC ACM TX EP
< #define ENDPOINT5_CONFIG ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_UNUSED // VENDOR RX EP
< #define ENDPOINT6_CONFIG ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_BULK // VENDOR TX EP
<
Code:
//new file usb_vendor.c, based entirely on usb_rawhid.c
#include "usb_dev.h"
#include "usb_vendor.h"
#include "avr/pgmspace.h" // for PROGMEM, DMAMEM, FASTRUN
#include "core_pins.h" // for yield(), millis()
#include <string.h> // for memcpy()
//#include "HardwareSerial.h"
#include "debug/printf.h"
#ifdef VENDOR_INTERFACE // defined by usb_dev.h -> usb_desc.h
#define TX_NUM 4
static transfer_t tx_transfer[TX_NUM] __attribute__ ((used, aligned(32)));
DMAMEM static uint8_t txbuffer[VENDOR_TX_SIZE * TX_NUM];
static uint8_t tx_head=0;
#define RX_NUM 4
static transfer_t rx_transfer[RX_NUM] __attribute__ ((used, aligned(32)));
DMAMEM static uint8_t rx_buffer[VENDOR_RX_SIZE * RX_NUM] __attribute__ ((aligned(32)));
static volatile uint8_t rx_head;
static volatile uint8_t rx_tail;
static uint8_t rx_list[RX_NUM + 1];
static volatile uint32_t rx_available;
static void rx_queue_transfer(int i);
static void rx_event(transfer_t *t);
extern volatile uint8_t usb_configuration;
void usb_vendor_configure(void)
{
printf("usb_vendor_configure\n");
memset(tx_transfer, 0, sizeof(tx_transfer));
memset(rx_transfer, 0, sizeof(rx_transfer));
tx_head = 0;
rx_head = 0;
rx_tail = 0;
usb_config_tx(VENDOR_TX_ENDPOINT, VENDOR_TX_SIZE, 0, NULL);
usb_config_rx(VENDOR_RX_ENDPOINT, VENDOR_RX_SIZE, 0, rx_event);
int i;
for (i=0; i < RX_NUM; i++) rx_queue_transfer(i);
}
/*************************************************************************/
/** Receive **/
/*************************************************************************/
static void rx_queue_transfer(int i)
{
void *buffer = rx_buffer + i * VENDOR_RX_SIZE;
arm_dcache_delete(buffer, VENDOR_RX_SIZE);
//memset(buffer, )
NVIC_DISABLE_IRQ(IRQ_USB1);
usb_prepare_transfer(rx_transfer + i, buffer, VENDOR_RX_SIZE, i);
usb_receive(VENDOR_RX_ENDPOINT, rx_transfer + i);
NVIC_ENABLE_IRQ(IRQ_USB1);
}
static void rx_event(transfer_t *t)
{
int i = t->callback_param;
//printf("rx event i=%d\n", i);
// received a packet with data
uint32_t head = rx_head;
if (++head > RX_NUM) head = 0;
rx_list[head] = i;
rx_head = head;
}
int usb_vendor_recv(void *buffer, uint32_t timeout)
{
uint32_t wait_begin_at = systick_millis_count;
uint32_t tail = rx_tail;
while (1) {
if (!usb_configuration) return -1; // usb not enumerated by host
if (tail != rx_head) break;
if ((systick_millis_count - wait_begin_at > timeout) || !timeout) {
return 0;
}
yield();
}
// digitalWriteFast(0, LOW);
if (++tail > RX_NUM) tail = 0;
uint32_t i = rx_list[tail];
rx_tail = tail;
memcpy(buffer, rx_buffer + i * VENDOR_RX_SIZE, VENDOR_RX_SIZE);
rx_queue_transfer(i);
//memset(rx_transfer, 0, sizeof(rx_transfer));
//usb_prepare_transfer(rx_transfer + 0, rx_buffer, VENDOR_RX_SIZE, 0);
//usb_receive(VENDOR_RX_ENDPOINT, rx_transfer + 0);
return VENDOR_RX_SIZE;
}
int usb_vendor_send(const void *buffer, uint32_t timeout)
{
transfer_t *xfer = tx_transfer + tx_head;
uint32_t wait_begin_at = systick_millis_count;
while (1) {
if (!usb_configuration) return -1; // usb not enumerated by host
uint32_t status = usb_transfer_status(xfer);
if (!(status & 0x80)) break; // transfer descriptor ready
if (systick_millis_count - wait_begin_at > timeout) return 0;
yield();
}
uint8_t *txdata = txbuffer + (tx_head * VENDOR_TX_SIZE);
memcpy(txdata, buffer, VENDOR_TX_SIZE);
arm_dcache_flush_delete(txdata, VENDOR_TX_SIZE );
usb_prepare_transfer(xfer, txdata, VENDOR_TX_SIZE, 0);
usb_transmit(VENDOR_TX_ENDPOINT, xfer);
if (++tx_head >= TX_NUM) tx_head = 0;
return VENDOR_TX_SIZE;
}
int usb_vendor_available(void)
{
if (!usb_configuration) return 0;
if (rx_head != rx_tail) return VENDOR_RX_SIZE;
//if (!(usb_transfer_status(rx_transfer) & 0x80)) return VENDOR_RX_SIZE;
return 0;
}
#endif // VENDOR_INTERFACE
Code:
//And finally, usb_vendor.h, also based entirely on usb_rawhid.h
#pragma once
#include "usb_desc.h"
#if defined(VENDOR_INTERFACE)
#include <inttypes.h>
// C language implementation
#ifdef __cplusplus
extern "C" {
#endif
void usb_vendor_configure(void);
int usb_vendor_recv(void *buffer, uint32_t timeout);
int usb_vendor_available(void);
int usb_vendor_send(const void *buffer, uint32_t timeout);
#ifdef __cplusplus
}
#endif
// C++ interface
#ifdef __cplusplus
class usb_vendor_class
{
public:
int available(void) {return usb_vendor_available(); }
int recv(void *buffer, uint16_t timeout) { return usb_vendor_recv(buffer, timeout); }
int send(const void *buffer, uint16_t timeout) { return usb_vendor_send(buffer, timeout); }
};
extern usb_vendor_class Vendor;
#endif // __cplusplus
#endif // VENDOR_INTERFACE
The only trouble i am having is making a c++ usb_vendor_class object available for sketches as a Vendor instance, as usb_inst.cpp refuses to compile, so i commented it out :
Code:
#ifdef VENDOR_INTERFACE
//usb_vendor_class Vendor;
#endif
I get :
Code:
/home/rd/.platformio/packages/framework-arduinoteensy/cores/teensy4/usb_inst.cpp:59:1: error: 'usb_vendor_class' does not name a type; did you mean 'usb_serial_class'?
59 | usb_vendor_class Vendor;
| ^~~~~~~~~~~~~~~~
| usb_serial_class
*** [.pio/build/teensy41/FrameworkArduino/usb_inst.cpp.o] Error 1
I probably forgot some code patching somewhere ?
And finally some little issue in enumeration :
When building with the vanilla USB_SERIAL compile flag this gives this enumeration on linux (see file usb_serial.png)
When building with the USB_VENDOR compile flag this gives this enumeration on linux (see file usb_vendor.png)
There is a mysterious interface that gets overwritten with the vendor interface i just added.
Sub=6a, Prot=c7.
What is that interface for ?
I will probably have to figure the interface ID and interface num defines and endpoint defines so as not overwrite it, unless someone knowledgeable as an idea.
ps : i am using 96 bytes tx maxkpacket sizes and 64 bytes rx maxpacketsize in the screenshots for my use case instead of the more general 512 byte size I provided in the code sample of this post.[/CODE]
Attachments
Last edited: