OS (e.g. Win10): Win10 PsychoPy version (e.g. 1.84.x): 3.1.5 32bit Standard Standalone? (y/n) yes What are you trying to achieve?: reading the parallel port with builder
I try to read the parallel port with builder because I want to use my response box instead of the keyboard for manual responses. I inserted a code element to read the port and it works as intended. I present a visual stimulus for the duration of one frame. The next routine in the flow reads the parallel port and does the rt-measurement. Stimulus still visible until keypress.
Code is as follows in the Begin Routine tab:
DONE = False
startrt = ppclock.getTime()
while not DONE:
pptaste = port.readData()
if pptaste != 255:
DONE = True
endrt = ppclock.getTime()
RT = ( endrt - startrt )
Why I’m writing this post? Because Michael states (in the context of other questions) “… If using Code components in Builder, we can’t use waitKeys() or loops, as Builder is structured around a screen refresh drawing loop that we can’t interrupt with code that will take longer than one screen refresh.” And that’s exactly what I do, I’m polling the parallel port with a loop of unknown duration. Shall I ignore my concerns? Better idea to realise my goal?
If you want a precise reading of the reaction time, then this sort of tight loop is useful (i.e. rather than checking just once per screen refresh, which is the way that Builder code is structured). The consequence, though, is that it stops everything else that Builder code does (e.g. checking whether it is time for new stimuli to be displayed, or terminating the routine after a set time, and so on).
So you can use code like this, as long as you are aware of the consequences (i.e. that nothing else will happen during this period, and that all Builder code is being put on hold). This can cause some unexpected behaviour during the rest of the routine, so you might just want to set continueRoutine = False immediately your trigger is detected, so that this routine will end on the current Builder screen refresh cycle and then the next routine will start afresh.
So in essence, there isn’t an absolute prohibition on using this sort of code, as long as you understand that it is breaking the normal Builder screen refresh cycle.
However, do be aware that tight loops like this cause performance issues if they last too long. i.e. your loop above could easily be called thousands or millions of times in succession without pause (depending on how the port.readData() function runs). This will starve all other processes on the computer of CPU time. i.e. this will drive Python to be consuming 100% of the CPU continuously. Eventually, some other processes are going to jump in and steal the CPU back, which could cause substantial hiccoughs in your timing. Normally we don’t have to worry about this with code that runs in a drawing cycle, because having a win.flip() on each cycle provides at least a few milliseconds of downtime on each cycle waiting for the next screen refresh, which is time that the CPU is available to other processes.
So with a tight loop like yours, I’d generally recommend putting a time.sleep(0.001) call on each iteration, to insert a 1 ms pause on each cycle, to allow sharing with other processes. However, I don’t think that works so well on Windows (as opposed to Linux and OS X), as the minimum sleep duration I think can be (?was?) much longer, taking you back into territory not much different from effectively checking just once per screen refresh. You’d need to look into these performance issues.
thank you very much for your detailed response. Now I’m much more confident in using my code fragment.
I checked CPU usage with and without a time.sleep(0.001) and of course it went down from approx. 25% to nearly nothing with the call to sleep. But minimum sleep duration was around 1 to 1.8 ms (measured with getTime()).
Again, thank you very much for your help,
yours - Sven