Before diving too deep into the software, there are some gotchas to be aware of…

  • Do not install the Adafruit_GFX, Adafruit_SSD1351 or Adafruit_ST7735 libraries offered by the Teensyduino installer! Use the Arduino Library Manager or install these manually from Github code. The Teensyduino-installed libraries sometimes diverge from the latest Adafruit code and might prevent this project from compiling.
  • When first building this project, please test initially with the canonical “Uncanny Eyes” sketch linked later in this guide, not anyone’s derivative code. This will help with any troubleshooting/support. Once the default code works, then you can try out variants that may be out there.

Teensy uses the Arduino environment for programming, so it’s pretty familiar and simple to work with, but it does require a little extra setup first…

If you’re not using a recent version of the Arduino IDE (1.6.5 or newer), this would be a good time to upgrade. Once you have that software installed and working, download and run the Teensyduino installer, which adds support for the full line of Teensy microcontroller boards in the Arduino IDE (but remember, don’t install the display/graphics libraries there…use the Arduino Library Manager instead).

From the Tools menu, select Board→Teensy 3.2 and CPU Speed→72 MHz (and whatever optimization settings you’d like). Confirm that you can compile and upload the classic “blink” sketch to the Teensy board. Don’t use the 96 MHz setting; the code actually performs a bit better at 72 MHz (due to SPI settings).

Do not continue until you have the Blink sketch working on the Teensy board.

Using the Arduino Library Manager (Sketch→Include Library→Library Manager…) install Adafruit_GFX, Adafruit_BusIO and Adafruit_ZeroDMA, plus the library compatible with your display: Adafruit_SSD1351 for the OLED display, Adafruit_ST7735 for TFT LCD.

(If you’re still using an oldschool version of the Arduino IDE, these libraries can be fetched from Github: Adafruit_GFX, Adafruit_BusIOAdafruit_SSD1351, Adafruit_ST7735 and installed manually.)

Finally, there’s the sketch code itself:

The sketch is utterly ginormous. In addition to several hundred lines of code in the main sketch, arrays containing graphics take up most of the space in the microcontroller’s flash program space.

Before uploading to the board, check lines 56 and 57 in the file config.h:

  #include <Adafruit_SSD1351.h>  // OLED display library -OR-
  //#include <Adafruit_ST7735.h> // TFT display library (enable one only)

One line is enabled, the other is commented out. By default, OLED is used. Comment out the opposite line if using TFTs.

If using all the same wiring as the previous page, it should be possible to compile and upload to the board and see some results…you should at least see an eye doing something. (If you changed the wiring, skip ahead to the next section below to make the code match, then return here.)

The sketch doesn’t compile!

Either Teensy support has not been correctly installed with the Teensyduino installer, or one or more of the libraries is not installed (Adafruit_GFX, Adafruit_SSD1351 or Adafruit_ST7735). Please see the notes at the top of this page regarding IDE & library compatibility.

The code compiles and uploads but nothing happens!

Check the connections between the display and Teensy board.

  • Did you enable the correct #include line for the display type (OLED vs TFT)?
  • Are you following the correct order-of-wires for the display type (OLED vs TFT)?
  • Are any wires off-by-one on the Teensy?
  • Any cold solder joints, or solder bridges between pads?
It kinda works, but the display is glitchy!
  • Keep your wires as short and as tidy as possible, check solder connections for good form. High speed SPI is really persnickety about connections.
  • Is the right CPU speed selected?

Still having trouble? Start a new thread in the Adafruit forums describing the symptoms. It’s extremely helpful if you can provide in-focus and well-lit photos that clearly show all the connections between the display and Teensy.

Do not continue until you see an eye. If it’s not doing exactly what you want, that’s okay, just need an eye to start.

Changing Appearance, Wiring and Inputs

Basic customization — enabling or disabling certain features, or changing pins assigned to functions — is done by editing the file config.h (it’s the second tab when the code is open in the Arduino IDE).

First section of this file controls the appearance of things.

The software includes a few ready-made eyes that can be used by simply enabling the corresponding #include line — remove the comment (//) at the start of that line, and make sure all the others are commented out. The stock sketch has “defaultEye” enabled:

defaultEye.h is human-ish in design. Okay, so the iris is anime-sized, but I’m so proud of that iris-scaling code I had to show it off as much as possible.

Some animals have such huge irises you rarely see the sclera (the white part of the eye). noSclera.h is an example eye for these situations.

dragonEye.h because dragons. It’s a moral imperative.

Goats (or is it Krampus?) have the weirdest pupils. goatEye.h is an attempt at simulating this. I designed this one to not move around, just to demonstrate how its done (the sclera image is the same size as the screen…no room to move…normally the sclera image is a bit larger).

Just above the eye selection is this line, disabled by default:


Normally the software renders the two eyes with distinct left and right shapes (there’s a caruncle — that corner area near the tear duct). Some projects only use a single eye and this shape just seems odd. Enabling SYMMETRICAL_EYELID uses a simpler “football shape” that’s neither left nor right…it’s a bit cartoonish but may look better on single-eyed creatures.

A little further down is a list indicating the pins used for each eye. The software can handle any number of eyes (it splits up time between them), but will usually be two (or, if compiling this for the Hallowing board, there’s just one):

eyeInfo_t eyeInfo[] = {
  { 39, -1, 2 }, // SINGLE EYE display-select and wink pins, rotate 180
  {  9, 0, 0 }, // LEFT EYE display-select and wink pins, no rotation
  { 10, 2, 0 }, // RIGHT EYE display-select and wink pins, no rotation

Each line contains three items: the pin that’s connected to the corresponding display’s OC (OLED chip select) or TCS (TFT chip select) pin, and another pin where a button (connected to ground) makes that eye independently wink. If you don’t want or need this wink control, the second pin value can be set to -1. Third item on each line is a rotation setting (0–3) for that screen:

  • 0 is the default orientation, with the display’s connector at the top.
  • 1 rotates the graphics 90 degrees clockwise (compensating for a display that’s installed 90° counterclockwise, with the connector at the left).
  • 2 rotates the display 180° (as on the Hallowing board).
  • 3 rotates 90 degrees counterclockwise (compensating for a display with the connector at right).

The stock code, as shown above, has two eyes. To work with just a single eye, comment out or delete one of the two lines. In the Hallowing case (if ADAFRUIT_HALLOWING is defined), this is already done (notice also the rotation value of “2,” because the Hallowing screen is oriented upside-down).

As previously explained, the next section decides whether to use the library for OLED or TFT displays, by #including one or the other:

#include <Adafruit_SSD1351.h>  // OLED display library -OR-
//#include <Adafruit_ST7735.h> // TFT display library (enable one only)

After this, two lines indicate the pins used for the displays’ DC (data/command) and RESET signals:

#define DISPLAY_DC     7       // Data/command pin for ALL displays
#define DISPLAY_RESET  8       // Reset pin for ALL displays

If the project involves multiple eyes, these lines should fan out from the microcontroller to ALL displays; unlike the select pins above, which are unique to each display.

The Hallowing board has a different set of pins already defined, plus some extra items for backlight control.

The last section configures various controls:

//#define JOYSTICK_X_PIN A0 // Analog pin for eye horiz pos (else auto)
//#define JOYSTICK_Y_PIN A1 // Analog pin for eye vert position (")
//#define JOYSTICK_X_FLIP   // If defined, reverse stick X axis
//#define JOYSTICK_Y_FLIP   // If defined, reverse stick Y axis
#define TRACKING            // If defined, eyelid tracks pupil
#define BLINK_PIN         1 // Pin for manual blink button (BOTH eyes)
#define AUTOBLINK           // If defined, eyes also blink autonomously
  #define LIGHT_PIN      A1 // Hallowing light sensor pin
  #define LIGHT_CURVE  0.33 // Light sensor adjustment curve
  #define LIGHT_MIN      30 // Minimum useful reading from light sensor
  #define LIGHT_MAX     980 // Maximum useful reading from sensor
  #define LIGHT_PIN      A2 // Photocell or potentiometer (else auto iris)
//#define LIGHT_PIN_FLIP    // If defined, reverse reading from dial/photocell
  #define LIGHT_MIN       0 // Lower reading from sensor
  #define LIGHT_MAX    1023 // Upper reading from sensor
#define IRIS_SMOOTH         // If enabled, filter input from IRIS_PIN
#if !defined(IRIS_MIN)      // Each eye might have its own MIN/MAX
  #define IRIS_MIN      120 // Iris size (0-1023) in brightest light
#if !defined(IRIS_MAX)
  #define IRIS_MAX      720 // Iris size (0-1023) in darkest light

JOYSTICK_X_PIN and JOYSTICK_Y_PIN (here set to A0 and A1, respectively) state where the joystick inputs are connected. By default, these lines are commented out — the eye moves autonomously, without user input. If you have a joystick connected, enable these two lines. (X_FLIP and Y_FLIP reverse the input direction if needed)

TRACKING sets whether the upper eyelid follows the pupil (as actual eyes do, it’s a neat thing). You can turn this off by commenting out this line.

LIGHT_PIN states where the photocell or dial is connected for adjusting the size of the pupil/iris. LIGHT_PIN_FLIP reverses the direction, like the joystick settings (lower values should be darker). LIGHT_MIN and LIGHT_MAX are the minimum and maximum reliable readings from the sensor (it might occasionally go below or above these, but it's rare, probably noise that can be disregarded), while IRIS_MIN and IRIS_MAX control the dilation of the pupil in response to the light reading (smaller numbers = smaller pupil, range is 0 to 1023).

IRIS_SMOOTH filters the input from LIGHT_PIN so it’s not twitchy. This slows the reaction time, but the movement is similar to real eyes, pretty nifty.

BLINK_PIN specifies a pin where a button is connected for blinking both eyes simultaneously (distinct from the individual left/right wink pins previously discussed). If you don’t have a button connected for this, the line can be commented out or the value set to -1.

AUTOBLINK (enabled by default) makes the eyes automatically blink randomly every few seconds. You can comment this out to make the eyes only blink with the buttons…or you can use both in combination.

If the example eyes don’t deliver quite what you need, it’s possible to generate new header files with custom graphics…

This guide was first published on Oct 29, 2015. It was last updated on Mar 08, 2024.

This page (Software) was last updated on Mar 08, 2024.

Text editor powered by tinymce.