Teensy USB delay

Status
Not open for further replies.

Maziac

Member
Hi,

this question is about the achievable delay of a Teensy device.

From my measurements it seems that the minimum lag I can achieve is 1-2x the polling interval.
I am trying to reduce this to about 0-1x polling interval.

Here is the (pseudo) code:

Code:
void loop() {
    
  // Endless loop
  while(true) {
 
    // There should be exact no packet in the queue.
    ASSERT(usb_tx_packet_count(JOYSTICK_ENDPOINT) == 0);
  
    // Prepare USB packet (note: this should immediately return as the packet queue is empty at this point.
    usb_joystick_send();
    
    // Wait on USB poll
    while(usb_tx_packet_count(JOYSTICK_ENDPOINT) > 0) {
    }

 
    // Wait until the poll interval is almost over (this is a routine which waits on a timer)
   waitForTime(JOYSTICK_INTERVAL*0.8);


    // Get joystick buttons and axis (sets the usb_joystick_data[])
    handleJoystick(); 
  }
}

E.g. if the JOYSTICK_INTERVAL USB poll interval is 10ms the joystick buttons are checked 2ms before the poll.
So this should result in a total lag of 2ms to 12ms depending on when the joystick button is pressed.

But what I'm measuring is 12 to 22ms. So exactly one more poll interval.

Is this to be expected. Is the usb packet maybe queued once more inside the HW?
 
Looks like you're calling the not-public API functions within usb_dev.c. That's fine... the code is open source and meant to be used. But since these aren't the normal public API, their precise functionality isn't really well documented....

In this case, looks like you're calling usb_tx_packet_count(). That function tells you how many packets are in a queue waiting to be given to the USB hardware. But it tells you nothing about how many the USB hardware has in it's possession. That info is stored in tx_state[], defined here:

https://github.com/PaulStoffregen/cores/blob/master/teensy3/usb_dev.c#L72

Those 6 possible transmit states boil down to the hardware having 0, 1 or 2 packets ready to transmit.

To make this work the way you want, you're probably going to have to add your own function inside usb_dev.c to read this hardware transmit state. Or maybe add code inside the USB interrupt handler, so you can detect when the state changes. But if you call any function from the interrupt, you need to be very careful about doing anything with the USB data structures. It's very easy to create race conditions or other subtle bugs if you're not extremely careful with how you use data from within interrupts.
 
Yes, I'm using a function from usb_dev.c. But I have been trying not to modify anything. I just include the usb_dev.h and usb_desc.h.
The only thing I modified so far was the descriptor and major change there was to change the poll time.

If possible I would like to continue not modifying the sources.
Would it be an option to check that tx_state is TX_STATE_BOTH_FREE_EVEN_FIRST or TX_STATE_BOTH_FREE_ODD_FIRST before entering the while loop.
To assure that there is no packet in the HW queue.

Then inside the loop it is guaranteed that only 1 new packet is generated for each poll.
This packet should be the one taken at the next poll.


I.e:

Code:
void loop() {
  
  // Wait till HW queue is empty
  while (tx_state[endpoint] != TX_STATE_BOTH_FREE_EVEN_FIRST  &&  tx_state[endpoint] != TX_STATE_BOTH_FREE_ODD_FIRST) {    
  }

  // Endless loop
  while(true) {
 
    // There should be exact no packet in the queue.
    ASSERT(usb_tx_packet_count(JOYSTICK_ENDPOINT) == 0);
  
    // Prepare USB packet (note: this should immediately return as the packet queue is empty at this point.
    usb_joystick_send();
    
    // Wait on USB poll
    while(usb_tx_packet_count(JOYSTICK_ENDPOINT) > 0) {
    }

 
    // Wait until the poll interval is almost over (this is a routine which waits on a timer)
   waitForTime(JOYSTICK_INTERVAL*0.8);


    // Get joystick buttons and axis (sets the usb_joystick_data[])
    handleJoystick(); 
  }
}
 
You'll probably need to modify the source, since tx_state is declared static.

But regardless of how you go about things, hopefully this info about how the USB device code works will help.
 
Yes, it did already. Thank you. But maybe you can still clarify if these assumptions are correct.

1. The ISR is called when the USB HW is polled.
2. When being polled: I guess the the HW will not wait on the packet written by the ISR but sent the packet from the previous ISR.
 
Best to check the reference manual for these finer points of exactly how the hardware responds.

But to summarize quickly, yes, when the USB hardware hears an IN token from the USB host, will only transmit a packet that has previously been given to it by its BDT. The BDT entries can queue 1 or 2 packets. If none are queue, it responds with a NAK token.

Again, full details are in the reference manual. When it comes to exactly how the hardware responds, this sort of answer can give you a basic idea but best to get it directly from the manual rather than trusting my words for the precise details.

Also worth mentioning this applies only to Teensy LC & 3.x. The USB hardware in Teensy 4.0 works differently. Teensy 4.0 supports 480 Mbit high speed USB, which can really help with low latency if you use polling every 125 us micro-frame rather than every 1 ms normal frame.
 
Something like this?

Code:
uint32_t usb_hardware_tx_packet_count(uint32_t endpoint)
{
	const usb_packet_t *p;
	uint32_t count=0;
	uint8_t state;

	endpoint--;
	if (endpoint >= NUM_ENDPOINTS) return 0;
	__disable_irq();
	state = tx_state[endpoint];
	for (p = tx_first[endpoint]; p; p = p->next) count++;
	__enable_irq();
	switch (state)
	{
	case TX_STATE_EVEN_FREE:
	case TX_STATE_ODD_FREE:
		return count + 1;
	case TX_STATE_NONE_FREE_EVEN_FIRST:
	case TX_STATE_NONE_FREE_ODD_FIRST:
		return count + 2;
	}
	return count;
}
 
Status
Not open for further replies.
Back
Top