EEG Trigger Codes

In a typical ERP experiment, we want to time-lock the onset of each stimulus --- and possibly when responses were made --- to the raw EEG data. That is, in the EEG data file we need some indicator at each time point when a stimulus or response occurred. The timing of this must be very precise, because we record EEG on the level of milliseconds, and 10--20 ms difference in the timing of ERP components can be significant. We call the markers that we send to the EEG recording trigger codes.

The challenge of precisely marking the timing of events in the EEG data file is complicated by the fact that we typically use a separate PC to present stimuli, and record responses, than the PC we use to record the EEG data. So we need a way of sending the trigger codes from the stimulus PC to the EEG recording PC with precise timing. Most modern connection standards, like USB or ethernet, had very fast timing but are not precise enough for EEG research. Instead, the way that has been in use for decades, and that many labs including ours still uses, is by sending trigger codes from the stimulus PC via the parallel port. This is a very old-fashioned connection standard that was originally used for connecting printers and other peripherals to a PC. PCs can send signals via the parallel port with very high temporal precision. A cable connects the parallel port on the stimulus PC directly to the EEG amplifier, which ensures that the EEG data file contains the marker at the precise time it was sent.

Parallel ports are no longer present on (probably) any PC since they are virtually obsolete. Fortunately, we can still buy them as add-on (PCI) cards that plug into the PC's motherboard. Alternatively, one can use a specialized device that connects to the PC via USB, such as the USB-1208FS. Special cables connect the parallel port on the stimulus presentation PC directly to the EEG amplifier. Then, during an experiment the trigger codes are saved directly in the EEG data file at the precise times they were received.

Using trigger codes in your experiment

At a minimum, every event in your experiment that you want to time-lock ERPs to (e.g., onset of stimuli; possibly behavioural responses as well) should be marked with an event code.

The biggest limitation of the parallel port is that it can only send trigger codes that are integers between 1-255. That is, in any experiment you cannot (easily) code for more than 255 different types of events. Moreover, in planning your experiment you will need to make a document (spreadsheets are good for this) that provide the mapping between trigger codes and their meanings. For example, in an experiment with two conditions, we might use the trigger code 1 to mark all events in condition A, and 2 to mark all events in condition B. If the experiment required a response, which could either be correct or incorrect, we might further use 3 to mark when a correct response was made, and a 4 to mark when an incorrect response was made.

Choice of trigger codes

What numerical value you select for each event type is largely arbitrary. You should try to avoid using a 1 because that value is often used for testing/calibration purposes — we have devices that will detect the appearance of a visual stimulus on a monitor (photocell) or onset of an auditory stimulus), and those devices send a 1 trigger code. If your experiment also uses that value, things get confusing quickly.

So within the range of 2–255, the choice is up to you. You can simply make a spreadsheet of each event type that you want to code for, and assign codes starting at 2 and going up. You can also devise more logical groupings. For some studies, people have used 2- or 3-digit codes in which each "place" in the number had a specific meaning. For example, in one study involving both auditory and visual stimuli in two languages, two-digit codes were used for audio stimuli, with the first numeral (the "tens") indicating the language (e.g., English = 1, Russian = 2), and the second indicating the stimulus category (e.g., match = 0, mismatch = 1). Then, three-digit codes were used for the visual (word) stimuli, with the hundres, tens, and ones places encoding diferent information about the stimuli.

In general, particularly due to the limit on the number of unique code values, we code stimuli by their category rather than their identity. That is, we code the type of condition (the levels of variables in the experimental design) rather than the identity of any specific stimulus (e.g., we don't code a picture of a cat as distinct from a picture of a dog, if animal pictures are one condition in the experiment). There are exceptions to this, and it can be helpful when doing some times of analyses (e.g., linear mixed effects) to know the identity of the item(s) presented on each trial. However, having a unique code for each trial, rather than each level of an experimental variable, actually can make ERP analysis more difficult — because you need to look for/average over a large number of values rather than one.

If you want to have the option of trial-level specifics later, an alternative to using trigger codes for this is to simply ensure that the necessary information from each trial is saved in the log file generated by your stimulus program. This is typically easy (if not the default), although as with everything you should pilot test and confirm this before running an entire experiment. The information from the log file can be merged with your EEG data post hoc, during preprocessing. Note that you absolutely still need to send trigger codes at the onset of each stimulus; but they can be generic categories rather than coding individual stimuli.

It is not uncommon (and often quite helpful) to send trigger codes even for events in your experiment that you don't think you are interested in — such as the onset of a fixation cross, or a resposne prompt. This can be useful later in checking the EEG data to ensure that things are happening at the expected times and in the expected sequence, or even for post hoc analyses (e.g., if you decide you want to look at EEG data from the start of a trial rather than just the onset of a specific stimulus within that trial).

It's also very helpful, if you are asking participants to make responses, to send a trigger code whenever a response is made. Moreover, if responses can be categorized as correct/incorrect, it is a really good idea to have your stimulus program determine if each response is correct or not, and send a code indicating correctness rather than just which button was pressed. This will make your life easier when analyzing the data.

Combinations of trigger codes

One simple way to get around the 2–255 trigger code limitation is to use pairs of trigger codes sent one after the other. Trigger codes require relatively little time (5-10 ms is safe) so sending a sequence of two shouldn't affect the timing of your experiment significantly. To ensure that the two codes don't get magled together, be sure that you send a 0 code in between each value (this is actually advisable in all cases — send a 0 after each code to ensure the parallel port resets to zero).

Document your trigger codes

It is critical that you store a document with your experiment that maps the trigger code values to meaningful labels. If you don't have this information, your data will not be interpretable. Like everything lab-related, it's important that this document be stored with your experiment on the NCIL server, and it's usually helpful to save it in the folder with your stimulus program(s) on the stimulus computer as well.

Sending trigger codes in your experiment

Finding the port address

If you are using a parallel port (the norm in NCIL), then you need to determine the hardwawre address of the parallel port. This is specific to the computer, and may change unexpectedly with software updates, and certainly if any hardware on the PC is changed. This "port address" is used in your stimulus program (see below for details). Typically in the lab this number is known for each computer and written down somewhere or in existing, functioning experiments on the stimulus PC.

Failing prior knowledge, to find the port address you need to open up the Device Manager app in Windows and go to Ports. Find the appropriate device/port and double-click, the select "Resources". You'll see an input-output range like 03F8-03FF. Then open the Windows Calculator app, switch to scientific mode, and set the mode to HEX. Enter the first value in that resource range (03f8 in our example), and input it into the Windows Calculator app. Then click DEC and it will conver to a decimal number (e.g., 03f8 converts to 1016). The DEC format number is your port address.

One easy way to confirm the port address is correct is using DirectRT. In the DIrectRT menu under Tools there is a IOTest option. If you select that, you can specify a port number and a trigger code value, and send it to the EEG system by clicking a button. Run the EEG acuisition software and confirm you see a code. If you don't, double-check the steps above, then ask Aaron for help :).

Copying the driver files to the experiment directory

This needs to be done for every experiment. Somewhere on the stimulus PC there should be a folder with the parallel port drivers. We try to put these in C:\Users\Public\InpOutBinaries_1501 in a folder called "copy to your experiment". In the worst case you can download the drivers — see setup instructions in the next section — and copy the files specified in those instructions to your experiment folder.

Using DirectRT

Sending triggers is easy with DirectRT, and well-documented in their manual. Just search for TTL and you will find the info. In brief, you send a code like presenting any stimulus, with "stim" being the value of the trigger code you want to send, "loc" being the port address, and "time" being 10 (we've found that shorter times can be unreliable). Unlike PsychoPy, you do not need to explicitly send a 0 event code after your non-zero event codes; DirectRT reliably resets the pins to zero after the "time" value you specify.

You should always send the event code associated with a stimulus before you present the stimulus. Otherwise the code will be sent after the stimulus has finished, which is not what you want.

As noted in the DirectRT manual, if you send a code as the last stim/loc/time event in a trial, it may not be sent. This can be an issue when sending event codes associated with responses. You can work around this by briefly presenting a blank screen or some other shenanigans after the event code.

Using PsychoPy

PsychoPy's documentation for the Builder instructs you to use the Parallel Out component to send codes via the parallel port (this has replaced the older "Code snippets" approach). The docs include how to specify the port address. Their documentation has the details so they won't be repeated here (especially since they've changed over time). This is a good starting point to get codes inserted in your experiment, but the timing of event codes sent via the Builder component is unreliable. When PsychoPy converts your Builder experiment into Python code, it treats even codes like other stimuli, adding a lot of unnecessary extra code, and moving them relatively far away in the program from the actual events they are supposed to be associated with.

The only way to get reliable even code timing in PsychoPy is to use the Coder. You can (and probably should) create your experiment in the Builder, including using code snippets to add event codes. Then, prior to running timing tests (let alone your actual experiment), conver the experiment to Coder view, find the lines that send event codes, and move then as close as possible to the lines that actually "flip" the screen to present stimuli (this includes auditory stimuli). Aaron has lots of experience with this and it's good to have him review your code/help do this. Do note however that this should be nearly the last thing you do, once you have got the rest of yoru stiulus program working. This is because, once you've modified the program in Coder, you can't go back to editing it in Builder (unless you want to re-do the modifications to send codes, which gets tedious really fast). That said, once we start working on a program in Coder we can usually made other optimizations so we rarely want to go back. Builder is a much easier way to get started though, so I don't recommend starting with Coder!

Last updated