If you insert a “code” component (from the “custom” component panel), you’ll see it has tabs in which you insert code to run at different times. e.g. for things that should only be done once, put them in the “begin experiment” tab, like these initial set-up tasks:
port = serial.Serial('COM6')
Then for things that should happen once per trial, put them in the “begin routine” tab, e.g. to initialise and reset certain variables like this:
pulse_started = False
pulse_ended = False
Then for things that should be done or checked continuously, put them in the “each frame” tab so that they run on each screen refresh (typically at 60 Hz):
if your_stimulus.status == STARTED and not pulse_started:
pulse_start_time = globalClock.getTime()
pulse_started = True
if pulse_started and not pulse_ended:
if globalClock.getTime() - pulse_start_time >= 0.01:
pulse_ended = True
This will be a bit sloppy with the duration of the pulse: it will always be at least 10 ms but will usually be longer than 10 ms, as the end of the pulse is likely to occur on the next screen refresh, which will typically be 16.7 ms later. If you want to have more precise control of the pulse width, look into suggestions like those by @habboud, or using threads.
You should avoid using
time.sleep() in a Builder script: durations as long as 10 ms will likely interrupt its attempts to maintain an active drawing cycle that keeps up with the screen refresh rate, which will damage your stimulus timing and performance. But realistically, it is the timing of the onset of the pulse that is most critical, and having it be of sufficient duration to be detected. If it runs a few milliseconds longer, that will not usually be an issue at all. So in this case, we haven’t used threads or
time.sleep(), at the cost of accepting that the pulse duration will be longer than 10 ms and a bit variable.