Poke an eye out with one of your mad scientific experiments?   Harness the awesome power of thermoplastic fusion to print a microprocessor powered bionic replacement!  It won’t help your vision much, but no one will doubt your mad scientist cred.

The Bionic Eye Module is a 46mm 3D-printed servo-powered mechanical eyeball module sized to fit into standard 50mm goggles.   2 sub-micro sized servos and an Adafruit Trinket create that annoying nervous tic.  The whole thing is powered by a 3xAAA battery pack.

This guide was written for the Trinket 5v board, but can also be done with the Trinket M0. We recommend the Trinket M0 as it is easier to use and is more compatible with modern computers!

Materials

Supplies

  • Stranded hookup wire.
  • Solder
  • Heat Shrink Tubing
  • Epoxy (‘Fast Cure’ or ‘5-Minute’ is preferred)
  • Double-sided foam tape
  • Paint - or permanent markers

Optional Supplies

  • Plastic Glue
  • Hot Glue

Tools

  • 3D Printer
  • Small Philips screwdriver
  • Soldering iron
  • Knife (e.g. X-Acto)
  • Toothpick

 

It is not necessary to poke an eye out to complete this project. In fact we strongly recommend keeping your natural eyes intact. But do use caution when wearing the 3D-Printed Bionic Eye. It will impair your vision.

This advanced ocular appliance gives you a close up view of the inner workings of a 3D printed bionic eye.  Of course you won't be able to see anything else out of that eye, but hey, it looks cool and that’s what counts!  

Nevertheless, use caution when wearing the Bionic Eye.  This costume prop will impair your depth perception and your vision in general.  It is best to remove the goggles while walking around.  Or wear them backwards to keep an eye on what’s behind you!

The Bionic Eye Module was modeled in OpenSCAD.  Both the OpenSCAD source and STL files can be found below.  This model should easily fit in the work envelope of most 3D printers.  It will work well with either ABS or PLA.  And since high strength is not required, you can print with minimal infill.

The prototype was printed on an UP! Plus2 using white ABS with 0.15mm Z- resolution and ‘hollow honeycomb’ fill.  Total print time was about 3 hours.

When the print is complete, remove any support material and lightly sand any rough spots.  Use a dental pick or a bent paperclip to clean out any support material from the screw holes.

Locate the center of the eye.  (It is where the eyeball was attached to the printing platform.)  Using permanent markers or paint (preferred), create an iris and pupil in the center of the eye.  You can use the concentric rings from the print layers as a centering guide.

OpenSCAD Source:

// Bionic Eye Module
// by Bill Earl
// for Adafruit Industries
// 

// Model of LS-0003 sub-micro sized servo
module LS0003_motor(c = "LightSteelBlue"){
	difference(){			
		union(){
			color(c) cube([19.5,8.5,16], center=true);
			color(c) translate([0,0,4.5]) cube([27.5,8.5,1], center=true);
			color(c) translate([5.5,0,2]) cylinder(r=3.9, h=19, $fn=20, center=true);
			color(c) translate([-3,0,1]) cylinder(r=0.8, h=16, $fn=20, center=true);
			color(c) translate([2,0,1]) cylinder(r=2.2, h=17, $fn=20, center=true);		
			color("white") translate([5.5,0,3.5]) cylinder(r=1.85, h=22.5, $fn=20, center=true);				
		}
		translate([10,0,-11]) rotate([0,-30,0]) cube([8,13,4], center=true);
		for ( hole = [11.5,-11.5] ){
			translate([hole,0,5]) cylinder(r=1, h=4, $fn=20, center=true);
		}	
	}
}

// Motor Bracket
module motorBracket() difference()
{
	cube([10, 5, 19]);
	translate([0,0,-0.5]) scale([2.2,1,1]) cylinder(20, r=4,, $fn=20);
}

// Model of complete motor assembly w/bracket.
module motorAssembly ()
{
	translate([0,0,-1.5])LS0003_motor();
	translate([0,12.5,0])rotate([-90,0,0])LS0003_motor();

	translate([10,14.5,-9.5])
	rotate([90,0,-90]) color("white") motorBracket();
}


radius = 23;
diameter = radius * 2;

// The Eyeball
rotate([90, 45, 0]) color("white") difference()
{
  sphere(radius, $fn=120);
  difference()
	{
		sphere(radius-1, $fn=120);
		translate([0,0,11]) cylinder(h=radius, r=radius);
	}

  	#translate([-5.5,0,radius - 22.75]) motorAssembly();
	translate([0,0,10]) cylinder(h=radius, r=1, $fn=20);
	translate([0,0,15.5]) cylinder(h=radius, r=2, $fn=20);
	rotate([0,0,90])translate([5,5,-9]) union()
	{
		cube([diameter, diameter, 20]);
		translate([0,0,10]) rotate([-90,0,0]) cylinder(h=diameter, r=10);
		translate([0,0,10]) rotate([0,90,0]) cylinder(h=diameter, r=10);;
	}
}


// Motor Assembly Bracket
color("LightSteelBlue") translate([radius, -radius, -radius]) difference() motorBracket();

// Eye cup insert
color("gray")translate([(radius * 2.5), 0, -radius]) difference()
{
	union()
	{
		translate([0,0,6])cylinder(10, r= radius, $fn = 120);
		translate([0,0,12]) cylinder(4, r= radius+2, $fn = 120);
		cylinder(6, r2=radius*0.6, r1=5, $fn=120);
	}
	translate([0,0,28]) sphere(radius + 2);
	translate([0,0,9]) cylinder(12, r= radius-1, $fn = 120);
	translate([0,0,-1]) cylinder(h=10, r=1, $fn=20);
	translate([0,0,1.5]) cylinder(h=10, r=1.85, $fn=20);

	difference()
	{
		cylinder(10, r=radius * 0.75, $fn=120);
		cylinder(10, r=radius * 0.3, $fn=120);
		translate([0,-20,0]) cube([20, 40, 20]);
	}
}

The tiny sub-micro sized servos used in this project are somewhat fragile.  The cases are just snapped together and held in place by the metallic adhesive label.  To make a more robust motor assembly, we’ll join the two motors with a small 3D printed bracket and some epoxy.

Remove the labels

First remove the metallic adhesive label and clean the adhesive residue so the epoxy can bond directly to the plastic case.  If the top or bottom plates pop off in the process, gently snap them back on.

Test Fit

Test fit the servo and bracket as shown.  Make sure that you have both wires coming out the same end.

Apply Epoxy

  • Mix a small amount of epoxy and apply it to the side of one servo with a toothpick or other small applicator.
  • Align the bracket with the bottom edge of the servo.
  • Apply more epoxy to the bottom and lower side of the second servo and attach to the first servo against the side of the bracket.

Tape until Set

Double check the alignment and tape the assembly together until the epoxy is cured.

The completed motor module should look like the photo below:

These little servos have some variability in response  from sample to sample.  Since the tilt servo will have a limited range of motion, you may need to adjust either the physical orientation or the code to keep it operating within that range.  The steps below should get you pretty close.

Use a servo horn to turn the shaft of the tilt servo fully counter-clockwise.

Slide the servo into the eyeball and align the shaft with the hole.

 

Position the motor assembly so that it is fully clockwise inside the eyeball and press the shaft into the hole.

Insert one of the longer screws into the hole from the outside and tighten it into the shaft.

Arrange the wires around the servo to avoid snags when turning.

Feed the wires through the slot in the eye-cup insert and align the other servo shaft with the hole. 

Insert another long screw through the back of the eye-cup insert and tighten.

Note: The holes are sized for a friction fit, but printer tolerances will vary. If the shafts are loose, add a drop of glue to the servo shafts to keep them in place.
The only difference between the wiring for the Trinket M0 and Trinket Mini is a single GPIO pin swap. The Trinket M0 uses GPIO #0 and #2 while the Trinket Mini using GPIO #0 and #1.

Wiring for the Bionic Eye module is pretty simple.  Just power, ground and 2 GPIO pins for the servo control.  

  • Clip the connectors from the ends of the servos and the battery holder.  
  • Solder two short pieces of stranded hookup wire to the Bat and GND pins of the Trinket.
  • Strip and twist the ground wire together with the brown wires from the two servos and solder.
  • Strip and twist the Bat wire together with the red wires from the two servos and solder.
  • Thread some heat-shrink tubing onto the two battery wires, then splice these onto the Bat and GND wires from the Trinket & Servos.
  • Slide the heat-shrink over the splice and heat to shrink it.

Trinket M0 Circuit Diagram GPIO #0, #2

Trinket Mini Circuit Diagram GPIO #0, #1

The Arduino code presented below works well on Trinket Mini boards. But if you have a Trinket M0 board you must use the CircuitPython code on the next page of this guide, no Arduino IDE required!

The code for the Bionic Eye is fairly simple: There are 2 servos, programmed to make independent random movements at random times.  The random ranges are selected so that the tilt servo moves on average about twice a second and the rotate servo moves about once per second.  You can speed it up for a somewhat wackier non-stop motion effect, or slow it down for a creepy/sinister evil-eye effect

The random range for the tilt servo motion is restricted to about 30 degrees.  The rotation servo is allowed to go the full 180 degrees.

The servos are allowed 100 milliseconds to reach their position, then they are detached to save power and reduce noise and jitter.

You will need to install the SoftServo Library.  You can download it using the button below.  For Library installation instructions, see this guide.

Then paste the following sketch into the IDE and upload it to your Trinket.

/*******************************************************************
  Bionic Eye sketch for Adafruit Trinket.  

  by Bill Earl
  for Adafruit Industries
 
  Required library is the Adafruit_SoftServo library
  available at https://github.com/adafruit/Adafruit_SoftServo
  The standard Arduino IDE servo library will not work with 8 bit
  AVR microcontrollers like Trinket and Gemma due to differences
  in available timer hardware and programming. We simply refresh
  by piggy-backing on the timer0 millis() counter
 
  Trinket:        Bat+    Gnd       Pin #0  Pin #1
  Connection:     Servo+  Servo-    Tilt    Rotate
                  (Red)   (Brown)   Servo   Servo
                                    (Orange)(Orange)
 
 *******************************************************************/
 
#include <Adafruit_SoftServo.h>  // SoftwareServo (works on non PWM pins)
 
#define TILTSERVOPIN 0    // Servo control line (orange) on Trinket Pin #0
#define ROTATESERVOPIN 1  // Servo control line (orange) on Trinket Pin #1
 
Adafruit_SoftServo TiltServo, RotateServo;  //create TWO servo objects
   
void setup() 
{
  // Set up the interrupt that will refresh the servo for us automagically
  OCR0A = 0xAF;            // any number is OK
  TIMSK |= _BV(OCIE0A);    // Turn on the compare interrupt (below!)
  
  TiltServo.attach(TILTSERVOPIN);   // Attach the servo to pin 0 on Trinket
  RotateServo.attach(ROTATESERVOPIN);   // Attach the servo to pin 1 on Trinket
  delay(15);                    // Wait 15ms for the servo to reach the position
}
 
void loop()  
{
  delay(100);  
  TiltServo.detach();   // release the servo
  RotateServo.detach();   // release the servo
 
  if(random(100) > 80)  // on average, move once every 500ms
  {
    TiltServo.attach(TILTSERVOPIN);   // Attach the servo to pin 0 on Trinket
    TiltServo.write(random(120, 180));    // Tell servo to go to position
  }
  if(random(100) > 90)  // on average, move once every 500ms
  {
    RotateServo.attach(ROTATESERVOPIN);   // Attach the servo to pin 1 on Trinket
    RotateServo.write(random(0, 180));    // Tell servo to go to position
  }
}
 
// We'll take advantage of the built in millis() timer that goes off
// to keep track of time, and refresh the servo every 20 milliseconds
// The SIGNAL(TIMER0_COMPA_vect) function is the interrupt that will be
// Called by the microcontroller every 2 milliseconds
volatile uint8_t counter = 0;
SIGNAL(TIMER0_COMPA_vect) 
{
  // this gets called every 2 milliseconds
  counter += 2;
  // every 20 milliseconds, refresh the servos!
  if (counter >= 20) 
  {
    counter = 0;
    TiltServo.refresh();
    RotateServo.refresh();
  }
}

Trinket M0 boards can run CircuitPython — a different approach to programming compared to Arduino sketches. In fact, CircuitPython comes factory pre-loaded on Trinket M0. If you’ve overwritten it with an Arduino sketch, or just want to learn the basics of setting up and using CircuitPython, this is explained in the Adafruit Trinket M0 guide.

These directions are specific to the "M0” boards. The original Trinket Mini with an 8-bit AVR microcontroller doesn’t run CircuitPython…for those boards, use the Arduino sketch on the “Arduino code” page of this guide.

Below is CircuitPython code that works similarly (though not exactly the same) as the Arduino sketch shown on a prior page. To use this, plug the Trinket M0 into USB…it should show up on your computer as a small flash drive…then edit the file “main.py” with your text editor of choice. Select and copy the code below and paste it into that file, entirely replacing its contents (don’t mix it in with lingering bits of old code). When you save the file, the code should start running almost immediately (if not, see notes at the bottom of this page).

If Trinket M0 doesn’t show up as a drive, follow the Trinket M0 guide link above to prepare the board for CircuitPython.

# Bionic Eye sketch for Adafruit Trinket.
#
# written by Bill Earl for Arduino
# ported to CircuitPython by Mikey Sklar
# for Adafruit Industries
#
# Required library is the Adafruit_SoftServo library
# available at https://github.com/adafruit/Adafruit_SoftServo
# The standard Arduino IDE servo library will not work with 8 bit
# AVR microcontrollers like Trinket and Gemma due to differences
# in available timer hardware and programming. We simply refresh
# by piggy-backing on the timer0 millis() counter
#
# Trinket:        Bat+    Gnd       Pin #0  Pin #2
# Connection:     Servo+  Servo-    Tilt    Rotate
#                 (Red)   (Black)   Servo   Servo
#                                   (Orange)(Orange)

import time
import random
import board
import pwmio
from adafruit_motor import servo

# we are intentionally avoiding Trinket Pin #1 (board.A0)
# as it does not have PWM capability
tilt_servo_pin = board.A2   # servo control line (orange) Trinket Pin #0
rotate_servo_pin = board.A1 # servo control line (orange) Trinket Pin #2

# servo object setup for the M0 boards:
tilt_pwm = pwmio.PWMOut(tilt_servo_pin, duty_cycle=2 ** 15, frequency=50)
rotate_pwm = pwmio.PWMOut(rotate_servo_pin, duty_cycle=2 ** 15, frequency=50)
tilt_servo = servo.Servo(tilt_pwm)
rotate_servo = servo.Servo(rotate_pwm)

# servo timing and angle range
tilt_min = 120      # lower limit to tilt rotation range
max_rotate = 180    # rotation range limited to half circle

while True:

    # servo tilt - on average move every 500ms
    if random.randint(0,100) > 80:
        tilt_servo.angle = random.randint(tilt_min, max_rotate)
        time.sleep(.25)

    # servo rotate - on average move every 500ms
    if random.randint(0,100) > 90:
        rotate_servo.angle = random.randint(0, max_rotate)
        time.sleep(.25)

This code requires an additional library be installed:

  1. adafruit_motor

If you’ve just reloaded the board with CircuitPython, create the “lib” directory and then download the Adafruit CircuitPython Bundle. You can copy 'adafruit_motor' folder into the lib directory.

$ mkdir /Volumes/CIRCUITPY/lib 
$ cp -pr adafruit_motor /Volumes/CIRCUITPY/lib

Unscrew the lens retainer and remove the lens from one side of the goggles. 

Thread the battery holder and Trinket through the open eye-cup.  Replace the lens with the Bionic Eye module and screw the lens retainer back in to secure it.

Fix the Trinket to the side of the eye-cup with a piece of double-sided foam tape.

 

Route the wires around the edge of the eye cup and fix with hot glue or tape.

Cut notches in the back of the battery holder wide enough for the goggle strap.

Re-assemble the battery holder around the strap.

Use caution when wearing the Bionic Eye.  This costume prop will impair your depth perception and your vision in general.  It is best to remove the goggles while walking around.  You can wear them on your forehead or around your neck.  Or wear them backwards to keep an eye on what’s behind you!

This guide was first published on Oct 13, 2014. It was last updated on Oct 13, 2014.