Introduction: Controlling Lights With Your Eyes

This semester in college, I took a class called Instrumentation in Biomedicine in which I learned the basics of signal processing for medical applications. For the class's final project, my team worked on EOG (electrooculography) technology. Essentially, electrodes attached to someone's temples send a voltage difference (based on the corneo-retinal dipole) to a circuit designed to filter and amplify the signal. The signal is fed to an ADC (analog-to-digital converter - in my case, the ADC of an Arduino Uno) and used to change the colors of a neopixel jewel.

This tutorial is a way for me to record what I've learned, and also share with the regular reader how signals are isolated from the human body (so be warned: it's full of extra detail!). This circuit can actually be used, with a few minor alterations, to motor hearts' electrical impulses as an EKG waveform, and much more! While it's certainly nowhere near as advanced and perfected as machines you'd find in a hospital, this eye-position-controlled lamp is great for an initial understanding and glimpse.

Note: I'm no expert in signal processing so if there are any errors or if you have suggestions for improvements, please let me know! I still have much to learn so commentary is appreciated. Also, many of the papers that I reference in links throughout this tutorial require academic access that I have courtesy of my university; apologies in advance for those who won't have access.

Side note: if you like this tutorial, votes in the Lights and Untouchables Contests would be appreciated. :)

Step 1: Materials

  • protoboard
  • resistors (100, 1k, 10k, 33k, 1M + 0.5M)
  • capacitor (0.1uF)
  • instrumentation amp (INA111 in my case, but there's a couple that should work relatively fine)
  • op amp (any - I happened to have an LM324N)
  • neopixel (any works, but I used a jewel)
  • 9V batteries x2
  • 9V battery headers x2
  • solid gel electrodes (electrode selection is discussed in step 5)
  • potentiometer
  • insulated wire
  • wire strippers
  • soldering iron + solder
  • alligator clips (with wires attached - solder some on if necessary)
  • hot glue (to stabilize wires that would be bent back and forth)
  • Arduino (pretty much board any works, but I used an Arduino Uno)

HIGHLY RECOMMEND: oscilloscope, multimeter, and function generator. Probe your outputs rather than just using my resistor values!

Step 2: Physiological Background and the Need for a Circuit

Quick disclaimer: I am by no means a medical expert in this field, but I compiled and simplified what I've learned in class/Googling below, with links for further reading if you'd like. Also, this link is by far the best overview of the subject that I found - includes alternative techniques.

EOG (electro-oculography) works on the corneo-retinal dipole. The cornea (front of eye) is slightly positively charged and the retina (back of eye) is slightly negatively charged. When you apply electrodes on the temples and ground your circuit to your forehead (helps to stabilize your readings and get rid of some 60Hz interference), you can measure around ~1-10mV voltage differences for horizontal eye movements (see the picture above). For vertical eye movements, place electrodes above and below your eye instead. See this article for a good read on how the body interacts with electricity -- great info on skin impedance, etc. EOGs are commonly used for diagnosing ophthalmological diseases such as cataracts, refractive errors, or macular degeneration. There's also applications in eye-controlled robotics in which simple tasks can be performed with a flick of the.. eyes.

To read these signals, i.e. compute the voltage difference between the electrodes, we incorporate an important chip called an instrumentation amplifier into our circuit. This instrumentation amp consists of voltage followers, a non-inverting amp, and a differential amp. If you don't know much about op amps, please read this for a crash course - essentially, they take an input voltage, scale it, and output the resultant voltage using its power rails. The integration of all resistors in between each stages helps with tolerance errors: normally resistors have 5-10% tolerance in values, and the regular circuit (not fully integrated in an instrumentation amp) would heavily rely on accuracy for good CMMR (see next step). The voltage followers are for high input impedance (discussed in above paragraph - major for preventing harm to patient), the non-inverting amp is to ensure high gain of the signal (more on amplification in next step) and the differential amp takes the difference between the inputs (subtracts the values from the electrodes). These are designed to crush as much common mode noise/interference as possible (for more on signal processing, see next step) for biomedical signals, which are rife with extraneous artifacts.

The electrodes face some skin impedance since your skin's tissues and fat obstruct the direct measurement of voltages, leading to the need for signal amplification and filtering. Here, here, and here are some articles in which researchers have attempted to quantify this impedance. This physiological quantity is commonly modeled as a 51kOhm resistor in parallel with a 47nF capacitor, though there are many variations and combinations. Skin in different locations can have different impedances, especially when you consider the different thicknesses and amounts of adjacent muscle. Impedance also changes with how well your skin is prepared for electrodes: thorough cleansing with soap and water is generally suggested to ensure excellent adhesion and consistency, and there are even specialty gels for electrodes if you really desire perfection. One key note is that the impedance changes with frequency (characteristic of capacitors) so you need to know your signal bandwidth in order to predict impedance. And yes, estimating impedance IS important for noise matching -- see later step for more info on this.

Step 3: Signal Processing: Why and How?

Now, why can't you just use the 1-10mV voltage difference as an immediate output to control LEDs? Well, there are lots of reasons for filtering and amplifying signals:

  • Many ADCs (analog-to-digital converters -- take your analog input and digitize them for reading and storing data on computer) simply can't detect such small changes. For example, the Arduino Uno's ADC is specifically a 10-bit ADC with 5V output, meaning that it maps 0-5V input voltages (out of range values will "rail," meaning that lower values will be read as 0V and higher values read as 5V) to integer values between 0 and 1023. 10mV is so small in that 5V range, so if you can amplify your signal to the full 5V range, small changes will be more readily detectible because they will be reflected by larger quantitative changes (5mV change to 10mV as opposed to 2V change to 4V). Think of it like a tiny picture on your computer: the details might by perfectly defined by your pixels, but you won't be able to differentiate shapes unless you expand the picture.
    • Note that having more bits for your ADC is better because you can minimize quantization noise from turning your continuous signal to discrete, digitized values. To calculate how many bits you need for ~96% retention of input SNR, use N = SNR(in dB)/6 as a rule of thumb. You also want to keep your wallet in mind though: if you want more bits, you need to be willing to shell out more money.
  • Noise and interference (noise = random artifacts that make your signals jagged instead of smooth vs interference = nonrandom, sinusoidal artifacts from adjacent signals from radio waves, etc) plague all signals measured from everyday life.
    • The most famous one is 60Hz interference (50Hz if you're in Europe and none in Russia because they use DC as opposed to AC for outlet power...), which is called utility frequency from the AC electromagnetic fields of power outlets. Power lines carry AC high voltage from electrical generators to residential areas, where transformers step down the voltage to the standard ~120V in American power outlets. The alternating voltage leads to this constant bath of 60Hz interference in our surroundings, which interferes with all types of signals and needs to be filtered out.
    • 60Hz interference is commonly called common mode interference because it appears in both of your inputs (+ and -) to op amps. Now, op amps have something called the common mode rejection ratio (CMRR) to reduce common mode artifacts, but (correct me if I'm wrong!) this is mainly good for common mode noises (random: noise instead of nonrandom: interference). To get rid of 60Hz, bandstop filters can be used to selectively remove it from the frequency spectrum, but then you also run the risk of removing actual data. Best case, you can use a low pass filter to only keep a range of frequencies lower than 60Hz, so everything with higher frequencies is filtered out. That's what I did for the EOG: the expected bandwidth of my signal was 0-10Hz (neglecting rapid eye movements -- didn't want to deal with it in our simplified version) so I removed frequencies greater than 10Hz with a low pass filter.
      • 60Hz can corrupt our signals via capacitive coupling and inductive coupling. Capacitive coupling (read up on capacitors here) occurs when air acts as a dielectric for AC signals to be conducted between adjacent circuits. Inductive coupling comes from Faraday's law as you run current in a magnetic field. There are lots of tricks to overcome coupling: you could use a grounded shield as a sort-of Faraday cage, for example. Twisting/braiding wires when possible decreases the area available for inductive coupling to interfere. Shortening wires and decreasing the overall size of your circuit also has the same effect for the same reason. Relying on battery power for op amp rails as opposed to plugging to a power outlet also helps because the batteries provide a DC source with no sinusoidal oscillation. Read much more here!
      • Low pass filters also get rid of a lot of noise, since random noise is represented by high frequencies. A lot of noises are white noise, meaning that noise is present for all frequencies, so limiting your signal bandwidth as much as possible helps with limiting how much of that noise is present in your signal.
        • Some low pass filters are called anti-aliasing filters because they prevent aliasing: when sinusoids are under sampled, they may be detected as a different frequency then they actually are. You should always remember to follow Nyquist's sampling theorem (sample signals at 2x higher frequency: need a sampling frequency of >2Hz for an expected 1Hz sine wave, etc). In this EOG case, I didn't have to worry about Nyquist because my signal was expected to mainly be in the 10Hz range, and my Arduino ADC samples at 10kHz -- more than fast enough to catch everything.
    • There are also little tricks to get rid of noise. One is to use a star ground so all parts of your circuits have the exact same reference. Otherwise, what one part calls "ground" may differ from another part due to slight resistance in wires, which adds up in inconsistencies. Soldering to protoboard instead of sticking with breadboards also reduces some noise and creates secure connections that you can trust as opposed to press-fit insertion.

There are plenty of other ways to suppress noise and interference (see here and here), but you can take a class on that or Google for more info: let's move on to the actual circuit!

Step 4: How the Circuit Works

Don't be intimidated by the circuit diagram: here's a rough breakdown of how everything works: (refer back to the previous step for some explanations too)

  • On the far left we have the electrodes. One is attached on the left temple, another on the right temple, and third electrode is grounded to the forehead. This grounding stabilizes the signal so there's less drift, and it also gets rid of some of the 60Hz interference.
  • Next is the instrumentation amp. Go back two steps for an explanation of what it does to generate the voltage difference. The equation for changing the gain of the amp is on page 7 of the data sheet [G = 1+(50kOhm/Rg) where Rg is connected on the amp's pins 1 and 8]. For my circuit, I adjusted to a gain of 500 by using Rg = 100Ohm.
  • After the instrumentation amp outputs the 500x amplified voltage difference, there's a first-order RC low pass filter, which consists of a resistor R_filter and capacitor C_filter. The low pass filter prevents anti-aliasing (no a concern for me though because by Nyquist, I need to sample at least 20Hz for an expected 10Hz bandwidth, and the Arduino ADC samples at 10kHz -- more than enough) and also cuts out noise at all the frequencies that I don't need. The RC system works because capacitors allow high frequencies through easily but obstruct lower frequencies (impedance Z = 1/(2*pi*f)), and creating a voltage divider with the voltage across the capacitor results in a filter that only allows lower frequencies through [cutoff for 3dB intensity is governed by the formula f_c = 1/(2*pi*RC)]. I adjusted my filter's R and C values to cut off signals higher than ~10Hz because the biological signal for EOGs is expected in that range. Originally I cut off after 20Hz, but after experimentation 10Hz worked just as well, so I went with the smaller bandwidth (smaller bandwidth is better to cut out anything unnecessary, just in case).
  • With this filtered signal, I measured the output with an oscilloscope to see my range of values from looking left and right (the two extremes of my range). That got me to about a 2-4V (because instrumentation amp gain was 500x for range of ~4-8mV), when my target is 5V (full range of the Arduino ADC). This range varied a lot (based on how well the person washed the skin beforehand, etc) so I didn't want to have that much gain with my second non-inverting amp. I ended up adjusting it to have a gain of only about 1.3 (adjust R1 and R2 in the circuit because gain of the amp = 1+R2/R1). You will need to scope your own output and adjust from there so that you don't go over 5V! Don't just use my resistor values.
  • This signal can now be fed into the Arduino analog pin for reading BUT the Arduino ADC doesn't accept negative inputs! You'll need to shift your signal up so that the range is 0-5V as opposed to -2.5V to 2.5V. One way to fix this is to attach the ground of your circuit board to the 3.3V pin of the Arduino: this shifts your signal up by 3.3V (more than 2.5V optimal but it works). My range was really wonky so I designed a variable offset voltage: that way, I could spin the potentiometer to center the range to 0-5V. It's essentially a variable voltage divider using the +/-9V power rails so that I could attach the circuit ground to any value from -9 to 9V and thus shift my signal up or down 9V.

Step 5: Picking Components and Values

    With the circuit explained, how do we pick which one (electrode, op amp) to use?

    • As a sensor, the solid gel electrodes have high input impedance and low output impedance: what this essentially means is that current can easily pass through downstream to the rest of the circuit (low output impedance) but would have troubling passing upstream back to your temples (high input impedance). This prevents the user from being injured by any high currents or voltages in the rest of your circuit; in fact, many systems have something called a patient protection resistor for additional protection, just in case.
      • Many different electrode types exist. Most people suggest Ag/AgCl solid gel electrodes for use in EKG/EOG/etc applications. With this in mind, you need to look up the source resistance of these electrodes (go two step back for my notes on skin impedance) and match it to the noise resistance (noise voltage in V/sqrt(Hz) divided by noise current in A/sqrt(Hz) -- see data sheets of op amps) of your op amps -- that is how you choose the correct instrumentation amp for your device. This is called noise matching, and explanations of why matching source resistance Rs to noise resistance Rn works can be found online like here. For my INA111 that I chose, the Rn can be calculated using the noise voltage and noise current of the data sheet (screenshot above).
        • There are PLENTY of articles evaluating electrode performance, and no one electrode is the best for all purposes: try here, for example. Impedance also changes for different bandwidths as reflected in the op amp data sheets (some data sheets will have curves or tables at different frequencies). Do your research but remember to keep your wallet in mind. It's nice to know which electrodes/op amps are best, but it's no use if you can't afford it. You'll need ~50 electrodes at least for testing, not just 3 for a one time use.
          • For optimal noise matching, not only should Rn ~= Rs: you also want noise voltage * noise current (Pn) to be as low as possible. This is considered more important than making Rn ~= Rs because you can adjust Rs and Rn by using transformers if necessary.
            • Caveats with transformers (correct me if wrong): they can be somewhat bulky and thus not optimal for devices that need to be small. They also build up heat so heat sinks or excellent ventilation are necessary.
          • Noise match only to your first initial amp; the second amp doesn't affect as much, so any op amp will do.

    Step 6: Building the Circuit

    Use the fritzing diagram above to build the circuit (second copy outlines what each part refers to in the circuit diagram from the previous step). If you need help identifying the LEDs in the diagram, use this resistor color code calculator, but the Rg of the instrumentation amp is 100Ohm, the R_filter is 1.5MOhm, the C_filter is 0.1uF, R1 of the non inverting amp is the 10kOhm, R2 is the 33kOhm, and the resistor for the potentiometer is the 1kOhm (potentiometer varies from 0 to 20kOhm). Remember to change your resistor values as needed to adjust gains!

    SAFETY NOTE: do NOT keep the batteries in when soldering, and do NOT NOT NOT put or solder the batteries on backwards. Your circuit will start smoking, capacitors will blow, and the breadboard might get damaged too. As a rule of thumb, only use the batteries when you want to use the circuit; otherwise, take them off (adding a flip switch for easily disconnecting the batteries would be a good idea too).

    Note that you should build the circuit piece by piece (check each stage!) and on a breadboard before soldering to a protoboard. First stage to check is the instrumentation amp: attach all rails (solder in battery holders), Rg, etc and use an oscilloscope on the output pin. For starters, use a function generator with a 1Hz sine wave with 5mV amplitude (or the lowest your generator will go). This is just to check that the instrumentation amp is working properly, and your Rg is providing your target gain.

    Next, check your low pass filter. Add that portion of the circuit and check your waveform: it should look exactly the same but less noise (jagged -- see last two images above). Let's probe your final output with an oscilloscope with your electrodes instead of a function generator now...

    Step 7: Testing Circuit With a Human

    Again, put electrodes at your left and right temples, and attach a ground wire to an electrode on your forehead. Only after that should you add batteries -- if any tingling occurs, remove IMMEDIATELY and double check connections!!! Now check your range of values when you look left vs. right and adjust R1/R2 of the non-inverting amp, as explained two steps ago -- remember that the target is a 5V range! See pictures above for notes on what to look out for.

    When you're happy with all the resistor values, solder everything to a protoboard. Soldering isn't strictly necessary, but it provides more stability over simple press fit joints and removes the uncertainty of the circuit not working simply because you didn't press them into a breadboard hard enough.

    Step 8: Arduino Code

    All code attached at bottom of this step!

    Now that you have a 5V range, you need to make sure it falls within 0-5V instead of -1V to 4V, etc. Either attach ground to the 3.3V pin of the Arduino or attach the offset ground voltage (orange wire above) to the ground rail and then connect a wire from the ground rail to GND pin of the Arduino (this is to shift the signal up or down so you fall within the 0-5V range). You'll have to play around: don't forget to scope your output whenever uncertain!

    Now for calibration: you want the light to change colors for different eye positions (looking far left vs. not as far left..). For that you need values and ranges: run EOG-calibration-numbers.ino to the Arduino with everything hooked up properly (finish off connections to the Arduino and neopixel according to my fritzing diagram). Not super necessary, but also run the code I have -- this will output a text file to your desktop so you can record all the values as you look left or right (python code was adapted from this example). How I did this was look left for 8 beats, then right, then up, then down and repeat for averaging later (see output_2.pdf for one log I kept). Press ctrl+C to force quit when you're satisfied. Using those values, you can then adjust the animations' ranges in my BioE101_EOG-neopixel.ino code. For me, I had a rainbow animation when I looked straight ahead, blue for far left, green for slight left, purple for slight right, and red for far right.

    Step 9: Future Steps

    Voila; something you can control with just your eyes. There's lots to optimize before it could make its way to a hospital, but that's for another day: the basic concepts are at least easier to understand now. One thing I'd like to go back and change is adjusting my gain to 500 for the instrumentation amp: looking back, that was probably to0 much because my signal afterward was 2-4V already and I had a hard time using the non-inverting amp to adjust my range perfectly...

    It's hard to get consistency because the signal changes SO MUCH for different conditions:

    • different person
    • lighting conditions
    • skin preparation (gels, washing, etc)

    but even so, I'm quite pleased with my final video proof of performance (taken at 3AM because that's when everything magically starts working).

    I know that a lot of this tutorial can seem confusing (yes, the learning curve was difficult for me too) so please feel free to ask questions below and I'll do my best to answer. Enjoy!


    PinkyPie80 (author)2017-06-22

    I love it! Is there a video too?

    watchmeflyy (author)PinkyPie802017-07-09

    There's a video at the bottom of the intro, and alternatively here's a link to two videos: one is a video in which you can see the waveforms on an oscilloscope, and the other video is of the actual light changes (my partner will say a color, and I'll look in the direction that color corresponds to). Enjoy!

    IbrahimB31 (author)2017-06-01

    Great! keep it on ^^

    watchmeflyy (author)IbrahimB312017-06-16

    Thank you!

    virpimt (author)2017-06-01

    mindblowing! This is so awesome! Thank You for sharing this knowledge. I voted
    You for the Lights and Untouchable Challenge. You really deserve to win :)

    watchmeflyy (author)virpimt2017-06-16

    Thank you for your kind comments!

    jammy1 (author)2017-05-29


    watchmeflyy (author)jammy12017-06-16


    WolfxPac (author)2017-05-23

    I couldn't find the electrodes and the ones sold online are too expensive and they don't sell pieces.
    can I use something else as a replacement??

    the_3d6 (author)WolfxPac2017-05-28

    Pretty much any gel electrodes will work just fine, you are not looking for precision here (not with these components and ADC). So your safe bet is to buy cheapest available, as long as they are AgCl-based. At least in our experiments with EMG, we found no significant difference in noise before we made much more complicated schematics with 16-bit ADC

    watchmeflyy (author)WolfxPac2017-05-24

    Hmm.. Electrodes do only coming in huge packs so it's inevitable that you'll have to shell out a bit to get them. You can buy cheaper electrodes like the aliexpress link, but just be warned that you do sacrifice quality. I got my pack of 100 pieces for about $30 dollars, so $0.30 each.

    PDegrave (author)2017-05-24

    Very good
    work, indeed and well documented.

    : in order to get a very high rejection of 60 Hz noise, you can use a sample
    rate of a multiple of 16.67 ms (1/60 Hz). Then, if you include in the program a
    delay corresponding to a loop time of 16.67 ms, you get a rejection of 60Hz noise.

    watchmeflyy (author)PDegrave2017-05-24

    Ah, thank you for the suggestion... Sorry if I'm missing something though, but, if you sample at 60Hz, wouldn't that result in aliasing of that 60Hz interference? And what is the delay's purpose?

    PDegrave (author)watchmeflyy2017-05-25

    Hi ! See my answer in the included image.
    I am not very familiar with the image upload process. I hope it works.
    Best regards.

    Sampling rate.pdf
    HardlyHumanFX (author)2017-05-24

    You're amazing.


    Ah shucks, thank you :)

    grebemic (author)2017-05-23

    Pretty cool

    watchmeflyy (author)grebemic2017-05-24

    Thanks :)

    TheThinker (author)2017-05-23

    Vote button is not working otherwise would have voted! Nice work!

    watchmeflyy (author)TheThinker2017-05-24

    Glad you enjoyed this!

    About This Instructable




    Bio: In which I turn the thoughts from my head into objects in my hands
    More by watchmeflyy:Controlling Lights With Your EyesLED Firefly EarringsLayered Sculpture Lamps
    Add instructable to: