Teensy 4.1 USB device code: why are all the functions avoiding EP1?

liudr

Well-known member
Here are two functions in usb.c. They each return without configuring an EP if it is less than 2, which means EP0 and EP1. I understand that EP0 is configured elsewhere but why not allowing EP1 to be configured? I would like to emulate a printer that has two EPs, EP1 for OUT and EP2 for IN.

Code:
void usb_config_rx(uint32_t ep, uint32_t packet_size, int do_zlp, void (*cb)(transfer_t *))
{
	uint32_t config = (packet_size << 16) | (do_zlp ? 0 : (1 << 29));
	if (ep < 2 || ep > NUM_ENDPOINTS) return;
	usb_endpoint_config(endpoint_queue_head + ep * 2, config, cb);
	if (cb) endpointN_notify_mask |= (1 << ep);
}

void usb_config_tx(uint32_t ep, uint32_t packet_size, int do_zlp, void (*cb)(transfer_t *))
{
	uint32_t config = (packet_size << 16) | (do_zlp ? 0 : (1 << 29));
	if (ep < 2 || ep > NUM_ENDPOINTS) return;
	usb_endpoint_config(endpoint_queue_head + ep * 2 + 1, config, cb);
	if (cb) endpointN_notify_mask |= (1 << (ep + 16));
}

Later these transmit and receive functions also don't touch EP1:

Code:
void usb_transmit(int endpoint_number, transfer_t *transfer)
{
	if (endpoint_number < 2 || endpoint_number > NUM_ENDPOINTS) return;
	endpoint_t *endpoint = endpoint_queue_head + endpoint_number * 2 + 1;
	uint32_t mask = 1 << (endpoint_number + 16);
	schedule_transfer(endpoint, mask, transfer);
}

void usb_receive(int endpoint_number, transfer_t *transfer)
{
	if (endpoint_number < 2 || endpoint_number > NUM_ENDPOINTS) return;
	endpoint_t *endpoint = endpoint_queue_head + endpoint_number * 2;
	uint32_t mask = 1 << endpoint_number;
	schedule_transfer(endpoint, mask, transfer);
}

In the various USB device options, only USB_EVERYTHING has a line at the end that mentions EP1:

Code:
  #define ENDPOINT1_CONFIG	ENDPOINT_TRANSMIT_ONLY

None of the other options uses EP1. Is this just to reserve it for USB_EVERYTHING option or is there any limitation to EP1?

Thank you!
 
Sorry this is more something for Paul to answer.

As for USB_EVERYTHING - It won't work as there is not enough endpoints for everything... As such it is also not in boards.txt

Also not sure, but maybe related to
Code:
#ifdef EXPERIMENTAL_INTERFACE
	// configuration for 12 Mbit/sec speed
        // interface descriptor, USB spec 9.6.5, page 267-269, Table 9-12
        9,                                      // bLength
        4,                                      // bDescriptorType
        EXPERIMENTAL_INTERFACE,                 // bInterfaceNumber
        0,                                      // bAlternateSetting
        2,                                      // bNumEndpoints
        0xFF,                                   // bInterfaceClass (0xFF = Vendor)
        0x6A,                                   // bInterfaceSubClass
        0xFF,                                   // bInterfaceProtocol
        0,                                      // iInterface
        // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
        7,                                      // bLength
        5,                                      // bDescriptorType
        1 | 0x80,                               // bEndpointAddress
        0x02,                                   // bmAttributes (0x02=bulk)
        LSB(64), MSB(64),                       // wMaxPacketSize
        1,                                      // bInterval
        // endpoint descriptor, USB spec 9.6.6, page 269-271, Table 9-13
        7,                                      // bLength
        5,                                      // bDescriptorType
        1,                                      // bEndpointAddress
        0x02,                                   // bmAttributes (0x02=bulk)
        LSB(64), MSB(64),                       // wMaxPacketSize
        1,                                      // bInterval
#endif // EXPERIMENTAL_INTERFACE
 
Last edited:
I've seen this EXPERIMENTAL_INTERFACE but it's not enabled even under the USB_EVERYTHING. It's only mentioned under USB_SERIAL and commented out:

Code:
#if defined(USB_SERIAL)
  #define VENDOR_ID		0x16C0
  #define PRODUCT_ID		0x0483
  #define MANUFACTURER_NAME	{'T','e','e','n','s','y','d','u','i','n','o'}
  #define MANUFACTURER_NAME_LEN	11
  #define PRODUCT_NAME		{'U','S','B',' ','S','e','r','i','a','l'}
  #define PRODUCT_NAME_LEN	10
  #define EP0_SIZE		64
  #define NUM_ENDPOINTS		4
  #define NUM_USB_BUFFERS	12
  #define NUM_INTERFACE		2
  #define CDC_IAD_DESCRIPTOR    1       // Serial
  #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 EXPERIMENTAL_INTERFACE 2
  #define ENDPOINT2_CONFIG	ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_INTERRUPT
  #define ENDPOINT3_CONFIG	ENDPOINT_RECEIVE_BULK + ENDPOINT_TRANSMIT_UNUSED
  #define ENDPOINT4_CONFIG      ENDPOINT_RECEIVE_UNUSED + ENDPOINT_TRANSMIT_BULK

In USB_EVERYTHING, CDC_ACM interrupt IN takes EP1. So does the device controller have 16 EPs or not? I've not read enough docs to know this. There's just so much to read. The NXP doc is so poorly written that probably nobody proofread it after it's written or put it through a grammar check.
 
Last edited:
USB device mode endpoint #1 is reserved on Teensy 4. Do not use it. No, I am not going to comment further on this, other than to warn of future unspecified compatibility issues if you make use of something clearly documented as reserved.

If you want to emulate an arbitrary USB device, using the other USB port (which normally is only for host mode) is the way to go. The special reserved status of endpoint #1 on Teensy 4 only applies to the main USB port.

NXP's documentation leaves a lot to be desired, but it does pretty clearly say the hardware supports 8 endpoints on page 2253 and 2405. You can also see 8 endpoint control registers are defined on page 2435 and 2437, and 8 control bits are allocated in registers documented on pages 2484 to 2486.

Of course, the 8 endpoint limit (which includes endpoint 0 and 1, leaving 6 general bidirectional endpoints for purpose use) is for device mode only. In host mode, an arbitrary number of QH structures (one per endpoint in each connected device or hub) can be created as linked lists from the periodic schedule array and on asynchronous circular list, so host mode is essentially unlimited.
 
Thank you Paul! I'm reading the relevant pages. It'll take some time to digest that much stuff.

So quick question: If I reverse the roles, using the "device port" (OTG core 1?) as host and the "host port" (OTG core 2?) as device, I will be able to use EP1 etc. up to 7 unique numbers plus EP0 for device controller, and won't have restrictions on host controller?

Having a reserved EP1 sounds strange to me. Is it common among arm processors? For smaller MCUs such as ATMEGA32U4, which you used for an earlier version of Teensy, EPs go from 0 to 6 so you can build a device with say EP1 EP2. If you want to emulate a device that has EP7, you can kind of get away by mapping EP7 to an unused 1-6 number.

So if I understand correctly, you can use any EP number as long as there are a total of only 8 such numbers, such as 0, 2-6 then 14,15. So in the case of USB_EVERYTHING, it does use EP0,1-6, 9,10 so totally 9 of them including EP1. Does it mean USB_EVERYTHING option actually will NOT work? :)
 
Wow, so many questions. Here's quick answers.

USB_EVERYTHING definitely does not work on Teensy 4. That's why it's not in the Tools > USB Type menu when using Teensy 4.

In device mode, 8 endpoints means endpoints 0 to 7. Numbers 8 to 15 can't be used. They simply are not supported by the hardware in device mode.

Host mode works differently, allowing virtually unlimited endpoints and devices / hubs.

EP1 reserved is Teensy 4 specific and applies only to the main USB port running in device mode.

Running the main USB port in host mode is theoretically possible But it's not very practical, because the main USB port (in device mode) is the only supported mechanism for loading new code onto your Teensy 4. Perhaps it could be done if you built special hardware with a chip like FSUSB30MUX to switch the USB signals away from your PC, under control of your program (and a resistor to control when your program isn't running), and connect to whatever USB device or hub you'll want connected while in host mode. But to upload a new program, obviously you need to connect those D+ & D- signal back to your PC. I suppose in theory a person with infinite patience could physically plug and unplug USB cables for each iteration of uploading new code and then running it. Of course you also need to arrange for Teensy 4 to have power in all modes.

The 2nd USB port can run in host or device mode. Today only host mode is officially supported. The hardware only has power support for host mode, so if you try device mode the VBUS power line does not have a way to power Teensy. Unofficially, I believe WMXZ published some code which is based on the normal USB device code from the core library, but with register names changed so it accesses the 2nd port. I don't have a link handy. Maybe someone knows and will reply with the link.

Device mode support on the 2nd port is among the top 10 most commonly requested features, so I'm considering putting PJRC engineering time into it later this year. Time frame or whether it will really happen at all are still uncertain, though I can say for sure it won't be before Teensyduino 1.57 is released.

Not sure if this answered every question, but I can't put more time into this message. Hopefully it helps anyway.
 
Thank you so much Paul! I know it must be hard to develop and manage such a successful product line including the software, hardware, and forum. I can see from the code I've read, such as core, and several libraries that 99.5% were your own work or your team. The rest was to stay compatible with arduino. ;)

I manage a successful product line, just at a much smaller scale. Constant requests for help, BOM management during this IC shortage, and other work. All that said, the help you and your team has provided has been stellar! I am working on a project that requires a lot of control of the USB at the level of custom driver/function and lower (emulating devices) so I have to dig deep into the device and host code for understanding of the controller. I have been reading your code, the 1060 doc, probing with USB analyzer, and relying on my prior experience with USB emulation and hosting at 1.1 version/speed.

I'm completely happy with your response. I was under the wrong impression that USB_EVERYTHING was a viable option and thus thought that EP1, EP9, and EP10 mentioned in it were all actually working. EP2-EP7 is fine.

I think for the short term I will use renumbering to get by and only emulate the most useful interfaces and ignore less useful interfaces (fingers crossed) to hope the host drivers are well written and will take the emulated device.

I'll explore the WMXZ version of code for some inspiration. I actually have a few of those usb mux chips from TI from another project a few years ago. 1:2 I believe. I had a project that has a 32U4 emulating keyboard with custom buttons for an educational toy. The computer I had froze up during boot if I had 32U4 plugged in during boot. Guess UEFI developer wasn't expecting an arduino at boot so I used the mux and another mcu to provide a few seconds of delay so the OS takes over USB.
 
So I am seriously considering running host mode on OTG1 (instead of on OTG2) and device mode on OTG2 so I can have all 8 EPs to work with. This stems from drivers of devices I want to simulate. Some drivers are smart enough to actually read conf desc to determine the EP# to work with so I can substitute EP1 with say EP3 and the driver will use EP3. But some dumb drivers probably only read VID:pID and says, "yep, I'm gonna use EP1 as BULK OUT" without parsing the conf desc that I've substituted say EP3 where EP1 was. So with dumb drivers my emulation doesn't work.

So I'm going to spend some major amount of time with this. I know it's all my responsibility to make sure everything is in theory supposed to work. So here goes my plan if anyone wants to critique:

Use OTG1 in host mode. The signals are physically connected to T4.1's microUSB port so I will need an OTG adapter for connection. I also have my breakout board for T4.1 so I can get it on a USB-A socket.

Use OTG2 in device mode. There will be up to 8 EPs to use including EP1. The signals are physically connected to T4.1's host 5-pin connection. I can use a 5-pin DuPont connector with a USB-A connector like this one:

https://www.amazon.com/Duttek-2-Pack-Motherboard-Adapter-Extender/dp/B06Y5RKMT8/


I don't need any drivers T4.1 comes with such as serial or keyboard etc. I wrote my own drivers. So I'm only going to need to modify the lowest layers of host and device code and watch for lots of hardcoded register names/locations and maybe even memory locations for QH and dQH. I will start by making definitions to replace the hardcoded things so I can switch which OTG core to use by #define. Once this work is done, I should be able to switch the two cores, or not but both should work.

I could add a startup delay before starting the host so I can disconnect the programming usb cable and connect OTG adapter and a device. I have on my breakout board a separate usb to power the board on its 5V reel so this will solve power issues. I don't need to switch on/off device's power, which I could do by adding a PMIC if necessary.

I'll cross compare my work with WMXZ's second-port device code as well.
 
Back
Top