Sending multiple triggers in one routine using serial port

OS : Win 10

PsychoPy version : V 2021.2.3

What are you trying to achieve?:

I’m trying to get Psychopy to send triggers to our brainvision EEG system through a serial port. The goal is to send triggers both when the stimuli (images) are presented and when the response key is pressed.

Below is the overview of the task in Psychopy:

I have a loop for the routine (ValenceRating) to show 1 image per iteration. Below is the condition file for this loop:

conditionfile

Below you can see the code I have so far in both “begin experiment” and “begin routine” tabs for triggers:

My questions:

  1. With a serial port, how can I read triggers from the condition file (is there a way to read through the trigger column when calling the triggers in Psychopy)? Right now I wrote “…” in the code in the “begin routine” tab because I didn’t know what should I placed there in order for the code to work.

  2. How to send triggers for more than one component in a routine? Right now with the code in the ‘begin routine’ tab, I expect Psychopy to send triggers for the first component in the routine. But I don’t know what to add to the code to also send triggers for the other components (for example in this task, third component (response key)) as well.

  3. Options for the possibility to integrate serial port as a component? (Similar to the parallel port component option). This will probably help us with the second question as well.

Thank you very much in advance.
Best wishes,
Somi

Hi Somi,

Thanks for your question - we’re working on making some materials to help people with this - so your questions are really helpful thankyou! In answer to your third question, yes I do think a component for serial port communication would be helpful.

I think this should work. Add a code component and in the “Begin Experiment” tab use:

import serial
port = serial.Serial('COM1')

In your Begin Routine tab use:

pulse_started = False
pulse_ended = False

in the Each frame tab use:

    if mything.status == STARTED and not pulse_started:# where "mything" refers to a visual thing you are trying to sync the trigger to (so you could have one of these for different component's in the same routine
        win.callOnFlip(port.write([0x01])) # send the trigger but only when the window flips to ensure syncronisation
        pulse_started  = True
        pulse_ended = False
        pulse_start_time = globalClock.getTime()
    if pulse_started and not pulse_ended:
        if globalClock.getTime() - pulse_start_time >= 0.005:
            pulse_ended = True

Oh and in terms of setting the trigger value through a conditions file. If you have a column with the header “trigger_value” and write what you want the trigger value to be on each trial, you should in theory be able to use this in the port.write() command. So instead of port.write([0x01]) it would be port.write([trigger_value]) - please let me know if that works!

I think that should do the trick - but please do let me know if not!!

Hope this helps
Becca

PS. to check the frame timing is write here we also highly recommend checking with a photodiode for visual stimuli if you can.

1 Like

Hello,

I have recently managed to send triggers via serial port, with a code similar to yours @Becca

Begin Experiment:
import serial
port = serial.Serial('COM3') 

Begin Routine:
pulse_started = False
pulse_ended = False

Each Frame:
if face_hab.status == STARTED and not pulse_started:
    port.write([trigger_values_hab]) # trigger_values_hab is the header of an excel coloumn where I define the triggers
    pulse_started = True
    pulse_ended = False
    pulse_start_time = globalClock.getTime()

if pulse_started and not pulse_ended:
    if globalClock.getTime() - pulse_start_time >= 0.005:
        pulse_ended = True

However I have some issues and any help would be appreciated:

1. I want to use the win.callOnFlip function as described in the link above and as can be seen below:

win.callOnFlip(port.write([trigger_values_hab])) 

But I receive this error:

“C:\Users\kppadmin\Desktop\Metin_Paradigm_2\Paradigm_2_ThirdTry_lastrun.py”, line 1451, in

win.flip()

File “C:\Program Files\PsychoPy\lib\site-packages\psychopy\visual\window.py”, line 1219, in flip

callEntry['function'](*callEntry['args'], **callEntry['kwargs'])

TypeError: ‘int’ object is not callable

I have also tried to change the settings in the properties of the excel coloums (I defined the values as “Text”, rather than “Standard”. Because otherwise the first code I wrote above did not work either…) But then, I receive this error:

data = to_bytes(data)

File “C:\Program Files\PsychoPy\lib\site-packages\serial\serialutil.py”, line 68, in to_bytes

return bytes(bytearray(seq))

TypeError: ‘float’ object cannot be interpreted as an integer

Any idea how I can make the win.callOnFlip function work while indexing to my trigger list on the excel sheet?

2. I have realized that not all the triggers are sent to the EEG. And I have figured out a pattern: If, let’s say, two identical triggers (I give the triggers values like 1,2 and 3 on the excel sheet) appear sequentially, then only the first one is being sent. For example, if I have stimuli_2 appear two times sequentially (meaning have two "2"s on the trigger_list_hab coloumn one coming after the other in rows), then a trigger called S2 is sent for the first stimuli_2, but not for the second stimuli_2. Do you know maybe what causes this?

Thanks in advance!

Hi There!

For win.callOnFlip, you need to call the function slightly differently the first argument would be the name of the function and the second would be the input to the function i.e.

win.callOnFlip(port.write, [trigger_values_hab)

regarding the second point - I am not sure about that - have you tried with values other than 1, 2, 3 ?

Becca

1 Like

Hi @grimfandangosh,

Can I also just confirm that you’re re-setting the port back to 0 before sending a second trigger?

Thanks!

Kim

1 Like

Hi @Becca and @Kimberley_Dundas

Thank you both for your fast replies.

@Becca I have tried the win.callOnFlip function again and I have not received any error this time, when I wrote the code as you suggested. However, triggers are either not sent or sometimes are sent and sometimes not… So, something is not right. I could not figure out what is the issue yet. I have tried changing the properties of the excel coloumn (I have realized when the properties are set to “Text”, only then the port.write function works for me. But I thought maybe this won’t be the case when I use win.callOnFlip. In fact, when I changed to properties to “Number” or simply “Standard”, the triggers started working but as I said, not consistently - i.e some triggers did not appear) Thank you for your help!

@Kimberley_Dundas Well actually, I was not re-setting the port back to 0. Now I am doing that and I can send multiple identical triggers one after another. :slight_smile: Thanks! Putting my code below:

if pulse_started and not pulse_ended:
    if globalClock.getTime() - pulse_start_time >= 0.005:
    port.write ([0x00]) # that was the missing part!
    pulse_ended = True

I would also like to ask some unrelated questions.

1.What does the number “0.005” actually refer to in the code above? Is it related to the trigger duration? If I am correct the trigger durations need to be set according to the sampling rate of the recording device. Although I am not sure where to define the duration.
2. I have seen somewhere else that timeout variable can be defined within the serial.Serial function. What does this variable actually refer to and is it important to define it?

Thanks in advance :slight_smile:

Hi @grimfandangosh,

  1. Yes you’re perfectly correct, the 0.005 is the duration of the pulse - as in it will be set to your trigger value for 0.005s and then set back to 0 after that. So yes, if you wanted to change the duration you would need to change it in the code you’ve pasted here.
  2. Regarding timeout - this relates more to reading from the port than sending data to it from what I understand. It relates to whether you want to wait until a certain number of bytes have been received before reading. You can read more on this here but the short answer is that no, you don’t need to set it for your use here.
1 Like

Hi @Kimberley_Dundas

thanks a lot for your response. It is all more clear now.

One more question though: is it definetely necessary to set the port back to 0? (I was not doing it until I realized two identical triggers cannot be sent without doing it… But other triggers were working fine)

Thanks! :slight_smile:

Hi @grimfandangosh,

No problem!

Re- setting the port to something is necessary, otherwise you’re setting your variable pulse_ended to True but not actually making any changes to the port itself. Therefore when you want to send the same value on the next trial, no changes are made. So if the port was set to send a 2 trigger on one trial, and then the next trigger is also 2, no changes are made as the port is already sending the value you’ve asked for.

What you don’t have to do is re-set to zero though. If you wanted to send a trigger at the start of the routine and then a separate trigger to mark the end of the routine, you could set the port to a given value at the start of the routine and then set it to say 5 at the end of the routine.

1 Like

Hi @Kimberley_Dundas

thanks again for the explanation :slight_smile: