Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 8 of 8

Thread: Teensy USB delay

  1. #1
    Junior Member
    Join Date
    Sep 2019
    Posts
    19

    Teensy USB delay

    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?

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    10,919
    which Teensy is in use might be important.

  3. #3
    Junior Member
    Join Date
    Sep 2019
    Posts
    19
    Teensy LC.

  4. #4
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,467
    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/co.../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.

  5. #5
    Junior Member
    Join Date
    Sep 2019
    Posts
    19
    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(); 
      }
    }

  6. #6
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,467
    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.

  7. #7
    Junior Member
    Join Date
    Sep 2019
    Posts
    19
    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.

  8. #8
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,467
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •