Serial ports (EEG - Brain Products and GSR - Biopac) interfere with each other causing errors in sending triggers

Hi all,
I fixed this issue by changing the code but I wanted to share the issue with the community to try to understand why the problem arose in the first place.
We recently changed the system in our EEG lab, and now we are using a serial port instead of a parallel port to get the computer which records the EEG signal to communicate with the computer which displays the stimuli and sends triggers.
Since we changed the system, when both GSR and EEG code components to send triggers are active, they seem to interfere with each other. To be precise, the USB-TTL box does not function properly (.e.,g red light stays one while should go off once the pulse has ended) and Acknowledge 5.0 (Biopac software used to record the signal) does not receive any triggers.
If I deactivate one of the two components, either GSR or EEG, the problem ceases to exist. The code I used was exactly the same, the only thing that changed was the name of the port obviously and it worked perfectly fine when I used a serial + parallel port (while now I use serial + serial).
Here’s the old code which caused the issue:

BEGIN ROUTINE
stimulus_pulse_started = False
stimulus_pulse_ended = False

EACH FRAME
if main_movie.status == STARTED and not stimulus_pulse_started:
win.callOnFlip(ser.write, str.encode(‘80’))
stimulus_pulse_start_time = globalClock.getTime()
stimulus_pulse_started = True

if stimulus_pulse_started and not stimulus_pulse_ended:
if globalClock.getTime() - stimulus_pulse_start_time >= 0.5:
win.callOnFlip(ser.write, str.encode(‘RR’))
stimulus_pulse_ended = True

I changed the GSR code to this, which fixed the issue:

BEGIN ROUTINE
win.callOnFlip(ser.write, str.encode(‘80’))
pulse_duration = 0.5 # set this to whatever pulse duration you need in seconds
pulse_end_trigger_sent = False # will turn to true in each frame tab once sent

send the trigger when the window flips (i.e. stimulus is drawn)

port is set up in set_up_port code component

EACH FRAME
if t > pulse_duration and not pulse_end_trigger_sent:
win.callOnFlip(ser.write, str.encode(‘RR’))
pulse_end_trigger_sent = True

As far as I know, the new code is not as accurate in terms of timing compared to the old code. Given the the GSR worked in the range of seconds rather than milliseconds, I thought it was better to change the GSR code rather than the EEG code. Is this correct?

I did some research starting with my intuition that the problem could potentially be in the the fact that now I am using a serial port to communicate with both the EEG and the GSR machines, but I am not sure this was the right intuition.
Here’s what I found:
Parallel Port is Non-Blocking, Serial Ports Can Cause Delays

  • In my old system, the EEG used a parallel port, which sends triggers as instant electrical pulses at the hardware level.
  • Parallel ports are non-blocking—they don’t interfere with the CPU or delay other processes.
  • The GSR system used a serial port, but it was the only serial device in use, so it had uninterrupted access to the serial communication channel.

:small_blue_diamond: Why It Worked Before:

  • The EEG parallel trigger happened instantly, and the GSR serial trigger had no competition for CPU time.

2. Now, Both EEG and GSR Use Serial Ports, Competing for Resources

  • Serial ports (COM3 for EEG, COM4 for GSR) are handled by the CPU and require the system to process data in sequence.
  • When the EEG system is active, it might be sending or receiving data constantly over COM3.
  • If PsychoPy tries to send a trigger to the GSR on COM4 while the EEG is actively communicating, the GSR trigger might be delayed, blocked, or lost.
    • Both EEG and GSR are competing for CPU and serial communication bandwidth, causing timing delays that didn’t exist when EEG was using a parallel port.

Old Code Uses globalClock.getTime() to End the Trigger

  • The trigger is turned off when globalClock.getTime() reaches a certain value.
    If EEG processing affects PsychoPy’s timing, the globalClock.getTime() condition might not be checked at the right moment, delaying or missing the GSR trigger reset.
  • If the GSR device expects a specific trigger timing, but the reset happens too late or not at all, the GSR might fail to register the trigger correctly.

New Code Uses a Simpler Timing Method (t > pulse_duration)

  • Instead of relying on globalClock.getTime(), the new code just checks t > pulse_duration.
  • t is trial time, updated by PsychoPy every frame, making it more reliable and less affected by EEG processing delays.
  • This ensures the trigger is turned off at the exact right time, preventing GSR detection issues.
    Any thoughts?
1 Like