Probably really basic timing question... Teensy 3.2

Status
Not open for further replies.

josephcesario

New member
Hi everyone:

I have what I imagine is a really basic and easy question about timing, so my apologies if the answer is obvious. What follows is a description of how and why I'm doing what I'm doing, but the basic question boils down to: Am I doing whatever I can to get the best timing resolution, in terms of how quickly my host computer reads signals from the Teensy? Or am I missing something obvious that I should do to make the host computer register information from the Teensy more quickly?

I'm using Teensy 3.2 (which is great!) to register how quickly someone pushes a button after seeing an image. The basic setup is simple: A software program (PsychoPy) displays an image on the computer screen. A person has to push a mechanical button as soon as they see the image. The software program registers the button press and provides the timing by linking the button press with when the image was presented.

I use the Teensy to capture the button press by connecting the two terminals on the mechanical button to the ground and the digital pin on the Teensy (pin 5 in the code below). As you can gather from the code, when the person presses the button, the Teensy sends that the Key Z has been pressed. The software program reads this as the keyboard Z key having been pressed and registers the time it took the Z key to be pressed relative to the start of displaying the image, thereby giving me the time it took the person to respond.

**So the software program displays an image, the person presses a button, the Teensy registers that and says that the Z key has been pressed, and the software program reads that the Z key was pressed and tells me how long it took the person to respond.**

(sidenote: I'm basically using the Teensy to pretend that it's a keyboard because the software program can easily read keypresses and does not as easily read other kinds of inputs that the Teensy might be able to provide.)

My understanding is that usb devices have some variable amount of timing lag because the host computer polls the usb device every X milliseconds, not continuously. If the person presses a keyboard key at 2 milliseconds after the image appears, but the computer doesn't poll the usb device for another 5 milliseconds, I will incorrectly see that it took the person 7 milliseconds to respond instead of 2. I'm trying to use the Teensy to avoid this kind of delay, if at all possible.

My question is, is the code below (which works perfectly well) missing something? Is the code sending the information from the Teensy to the host computer as soon as it's obtained or is there some other way of doing this? Basically, I want the host computer to be able to read the Z keypress from the Teensy absolutely as soon as possible.

Again, my apologies if there is an obvious answer here. I checked the existing documentation as best I could and I *think* I have it right, but I'm not sure.

Thank you!

CODE:

bool notCurrentlyPressed = true;

void setup() {
Serial.begin(38400);
pinMode(5, INPUT_PULLUP);
}

void loop()
{
if (digitalRead(5) == LOW) {

if (notCurrentlyPressed == true) {
Keyboard.set_key1(KEY_Z);
Keyboard.send_now ();

notCurrentlyPressed = false;
}
}

else {

if (notCurrentlyPressed == false) {

Keyboard.release (KEY_Z);

notCurrentlyPressed = true;
}
}
delay(50);
}
 
Using delay on every pass kills any accuracy.

What happens without it?
 
Last edited:
What you want is to debounce the button by stopping the code from firing a second 'z' for x ms after the initial one.

Try the delay right after the keyboard message is sent.
 
Last edited:
What you want is to debounce the button by stopping the code from firing a second 'z' for x ms after the initial one.

Try the delay right after the keyboard message is sent.

Hi oddson:

Thanks for the reply. Yeah, correct that if I remove the:

delay (50)

then it registers like 20 or 30 "Z"s every time the button is pressed.

Changing it to:

delay (1)

or some other smaller value seems to work just fine.

The question then is, is there anything else I can be doing in terms of how the overall method to decrease lag? Is this code still subject to the usb polling delay from the host computer, or does this code allow for the signal to be read by the host computer as soon as the teensy registers it?
 
A larger delay after send_now() should work adding to the latency at all.

USB latency is unavoidable without moving everything to the Teensy.
 
Actually I don't know enough to say that but I suspect the send now is the most imediate method.

I also think the latencies involved are very small compared with human reaction times if you avoid delays in the loop while waiting for the signal.
 
Hi Joseph

I would agree with the comments made thus far; the need to de-bounce the button press and the nominal 1ms USB polling delay from the host PC being of little consequence compared to human reaction times (more in the order of 20-40ms at a guess).

I would add that the following:

a) Given your current design, you appear to have optimised any latencies as far as you can, but...
b) That 1ms host-polling delay is the least of your concerns - the additional overhead introduced by the OS in feeding the simulated key-press to your application is likely to be both higher AND highly variable. Add to that any other USB devices currently active on the same USB hub (inside the host itself) and you can say goodbye to any form of deterministic/predictable timing.
c) USB as a 'real-time' communication channel really needs a more complex streaming/synchronising protocol applied before it becomes useful in this context. Effectively, the addition of (synchronised) timing data would need to be sent alongside each datum, time-stamping each event.
d) Without c. in place, there is no way to even know or measure what sort of latency has been introduced to each timing event.

USB is optimised for bandwidth over latency - works nicely enough once you've got a stream of data on the move, but any design that relies on deterministic latency in the 1-10 ms second range is unlikely to prove successful.

You might want to consider an alternative approach to using the USB channel for the sensitive timing. Typically, it should be possible to read pretty-much in real-time the state of either a serial CTS/RTS pin of a _true_ serial-port, or possibly one of the Parallel port pins.

Of course, that predicates having real ports on your application host platform - a rarity these days.

Otherwise to still use USB, keeping a timer running and sending the elapsed timer value(s) in place of the static 'Key_Z' could allow the host application to synchronise at least to the delta between key presses (even if it still can't really know when the timing data was sent in absolute terms.) If the application rigidly and reliably presented a new image at fixed intervals, this could work...

Good luck, Joseph.
 
can you work in reverse...??
Send an image to the PC screen from within the PC prog,
Send a signal from the PC to teensy via USB either immediately before or immediately after sending image to screen
Teensy can be programmed to measure the elapsed time between the signal received by Teensy and the button on Teensy pressed by user after seeing the image
There is only 1 delay to worry about.....the delay in the PC sending the signal to Teensy that image is displayed......and this will be only small milliseconds and fairly constant providing PC is reasonably fast and not overworked
 
Human reaction times are in tenths of seconds and vary wildly even by one person on repeated tries at the same test.

I assume the software used gives advice on avoiding latencies and you don't have virus scanning going on, etc..

If a small bias is added to all measurements it should have no impact on the results; it's the variable part that matters and that's exactly what your adding with a delay running while you are scanning.

But given all that I'd not worry about this source.... once you put the delay in the correct spot.

FYI this isn't conventional denouncing because you take the first LOW reading as the trigger so you run a risk of false starts but it minimizes latency.

If it's problematic in operation then I'd suggest the BOUNCE library instead as I believe you could subtract the applied latency directly from the results and it would be a minimal source of error.
 
You might want to look into the bounce2 library that is part of the Teensy release to do the software debouncing. In addition, it has state, and functions rose and fell to tell you when the pin transitioned from LOW->HIGH and HIGH->LOW respectively.:

The code would look something like:

Code:
#include <Bounce2.h>

Bounce button_press;

void setup () {
    Serial.begin (38400);
    pinMode (5, INPUT_PULLUP);
    button_press.interval (50);
    button_press.attach (5);
}

void loop ()
{
    // update button information
    button_press.update ();

    // Did the button go HIGH->LOW (i.e. button pushed with INPUT_PULLUP)
    if (button_press.fell ()) {
        Keyboard.set_key1 (KEY_Z);
        Keyboard.send_now ();
        // possibly add a delay here to wait for the system to display the image

    // Did the button go LOW->HIGH (i.e. button released with INPUT_PULLUP)
    } else if (button_press.rose ()) {
        Keyboard.release (KEY_Z);
        Keyboard.send_now ();
        // possibly add a delay here to wait for the system to clear the image
    }
}
 
My understanding is that usb devices have some variable amount of timing lag because the host computer polls the usb device every X milliseconds, not continuously. If the person presses a keyboard key at 2 milliseconds after the image appears, but the computer doesn't poll the usb device for another 5 milliseconds, I will incorrectly see that it took the person 7 milliseconds to respond instead of 2. I'm trying to use the Teensy to avoid this kind of delay, if at all possible.

I have some good news for you, at least with this specific question. Teensy's USB code already requests 1 ms polling for the endpoint used for USB keyboard. This is in usb_desc.c. Look for KEYBOARD_INTERVAL.

Of course, the USB host (your PC) has full control over the actual timing. Teensy only makes the request as part of the USB descriptor data your PC reads when it detects the device. Your PC's drivers *should* use this info to configure the USB controller to poll the endpoint every millisecond. The other good news is the USB EHCI hardware in your PC uses a special periodic schedule to absolutely guarantee the polling happens at precise intervals (1, 2, 4, 8, 16, 32ms...), so at least with HID using this type of endpoint, the concerns expressed above about interference from other USB devices should be a non-issue. Your PC's drivers should be managing the bandwidth allocated to all of the periodic schedule, but that's a rather complex issue about the design of the host controller drivers and except for Linux (which does do this properly), only people within Microsoft and Apple could really say how that part is done. But we do know the EHCI hardware is fundamentally design to guarantee the polling for this type of endpoint.


My question is, is the code below (which works perfectly well) missing something? Is the code sending the information from the Teensy to the host computer as soon as it's obtained or is there some other way of doing this? Basically, I want the host computer to be able to read the Z keypress from the Teensy absolutely as soon as possible.

Keyboard.send_now() is indeed the best you can do, at least from the Teensy side.
 
Status
Not open for further replies.
Back
Top