Overview and Parts
A recent MAKE project, Spooky Blinky Eyes by Bill Blumenthal, demonstrates an ATTiny45 processor fading a pair of LED eyes that randomly blink, giving a more realistic effect than standard "always on" LED eyes.
The effect is due to come clever programming of the timers available on the ATTiny processors featured on the Adafruit Trinket and Gemma microcontrollers. Pins 0 and 1 are capable of pulse width modulation. The timers are set to fade the pins in and out by changing the pulse width back and forth. The blink effect is using an algorithm called a linear feedback shift register (LFSR) to pseudo-randomly turn the eyes off and on quickly.
This project adapts the original code for use on the faster ATTiny85 processor and for the Arduino integrated development environment (IDE). It also adds a Cadmium Sulfide (CdS) photocell to allow the eyes to come on only below a certain light level. During sunlight, this will save battery power.
Parts List
- Adafruit Gemma M0, Trinket M0, Trinket Mini or Gemma v2 microcontroller board (if Trinket, either the 3.3V or 5Vtype works). We recommend using the Trinket M0.
- Cadmium Sulfide Photocell
- 2 LEDs (I used two 5 mm red for the evil look, you can use any size or color)
- (1k) ohm resistor
- Tiny Breadboard (or other suitable wiring surface)
- 6 volt coin cell battery pack
- 2 CR2032 Batteries
- A prop to put your circuit in
Page last edited March 08, 2024
Text editor powered by tinymce.
Wiring
The circuit is fairly straightforward and can be assembled well within an hour
Headers (included with Trinket) may be soldered to facilitate attachment to a breadboard or proto board. Other parts may be pressed into the breadboard. Ready-made hookup wire or cut to fit wires make the connections.
The same circuit with Gemma. The LEDs share D0. D2/A1 is hooked to the junction of the photocell and a 1000 ohm (1K) resistor. If you wish to just rely on the power pack on/off switch, you can eliminate the photocell and 1K resistor.
Page last edited March 08, 2024
Text editor powered by tinymce.
Arduino Code
Preparing Your Trinket
Be sure you have a 3 volt Trinket or Gemma. Follow the Introducing Trinket or Introducing Gemma tutorials rather carefully to ensure your Arduino integrated development environment (IDE) is set up.
Remember you must press the hardware reset button on the Trinket / Gemma then quickly press upload in the Arduino software to upload a sketch. If you get an error, try the reset-upload process again. If you continually cannot load the blink sketch, check to make sure the Trinket / Gemma is connected (without any wires connected to pins #3 and #4) and the Arduino IDE software has all the required changes. This project does not use pins 3 and 4, they are shared with the USB connector (Gemma does not have these pins).
The following uses PWM for the eye brightness. Controlling the counter which helps random blinks still uses hardware Timer 1. A random functionality could be used but would not give as nice a "randomness" and possibly not be timed right given all that is going on with the state machine.
// SPDX-FileCopyrightText: 2018 Anne Barela for Adafruit Industries
//
// SPDX-License-Identifier: GPL-3.0-or-later
/*
Name: Blinking Eyes - based on code by Brad Blumenthal, MAKE Magazine
License: GPLv3
Modified for 8 MHz ATTiny85 and low light photocell
October 2013 for Adafruit Learning System
*/
#define SENSITIVITY 550 // photocell sensitivity (changeable)
#define CELL_PIN 1 // CdS Photocell voltage divider on
// Trinket GPIO #2 (A1), Gemma D1/A1
uint8_t eyes_open;
volatile uint8_t blink_count;
volatile uint8_t blink_flag;
volatile uint8_t tick_flag;
volatile uint8_t getting_brighter = 0;
const uint8_t min_bright=16;
const uint8_t max_bright=128;
volatile uint8_t brightness;
uint8_t lfsr; // Linear Feedback Shift Register
const uint8_t min_blink = 64u; // don't blink more than once every 3 secs or so
void setup() {
pinMode(0, OUTPUT); // Eyes set as output
pinMode(2, INPUT); // Photocell as input
analogWrite(0, max_bright); analogWrite(1, max_bright); // Light eyes
eyes_open = 1;
blink_flag = 0;
lfsr = random(100); // initialize "blinking"
blink_count = max(blink_count, min_blink);
lfsr = (lfsr >> 1) ^ (-(lfsr & 1u) & 0xF0u); // pseudorandom blinking
// Timer1 set to CK/1024 ~10 (8) hZ at 8 MHz clock rate for blinking action
TCCR1 |= _BV(CS13) | _BV(CS11) | _BV(CS10);
TIMSK |= _BV(TOIE1); // Enable Timer/Counter1 Overflow Interrupt
}
void loop() {
uint16_t photocell;
photocell = analogRead(CELL_PIN);
if(photocell > SENSITIVITY) { // if too light, shut down eyes until it gets darker on photocell
tick_flag=0;
analogWrite(0,0); // Turn off eyes if too light out
}
if (tick_flag) { // if too bright or we've counted enough ticks (clocks for blink)
tick_flag = 0;
if (blink_flag) {
blink_flag = 0;
if (eyes_open) {
eyes_open = 0;
analogWrite(0,0); // Turn off eyes by stopping PWM
blink_count = (lfsr & 0x01) + 1; // off for 1-2 ticks
}
else {
eyes_open = 1;
analogWrite(0,brightness); // Turn eyes on
blink_count = max(blink_count, min_blink);
lfsr = (lfsr >> 1) ^ (-(lfsr & 1u) & 0xF0u); // regenerate pseudorandom blink
}
}
else { // One "tick," but we didn't blink... work on brightness control
if (getting_brighter) {
brightness += 2; // increase brightness
analogWrite(0, brightness);
if (brightness >= max_bright) getting_brighter = 0;
} else {
brightness -= 2; // decrease brightness
analogWrite(0, brightness);
if (brightness <= min_bright) getting_brighter = 1;
}
}
}
}
ISR (TIMER1_OVF_vect) { // Every 64 times a second, check blink
noInterrupts();
tick_flag = 1;
blink_count--;
if (!blink_count) {
blink_flag = 1;
}
interrupts();
}
The Arduino code compiles to 1626 bytes.
Page last edited March 08, 2024
Text editor powered by tinymce.
CircuitPython Code
Trinket M0 and Gemma 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 or Adafruit Gemma M0 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.
# SPDX-FileCopyrightText: 2018 Mikey Sklar for Adafruit Industries
#
# SPDX-License-Identifier: GPL-3.0-or-later
"""
Blinking Eyes - based on code by Brad Blumenthal, MAKE Magazine
License: GPLv3
"""
import time
import analogio
import board
import pwmio
try:
import urandom as random # for v1.0 API support
except ImportError:
import random
# Initialize photocell
photocell_pin = board.A1 # cds photocell connected to this ANALOG pin
darkness_max = (2 ** 16 / 2) # more dark than light > 32k out of 64k
photocell = analogio.AnalogIn(photocell_pin)
# Initialize PWM
# PWM (fading) - Both LEDs are connected on D0
# (PWM not avail on D1)
pwm_leds = board.D0
pwm = pwmio.PWMOut(pwm_leds, frequency=1000, duty_cycle=0)
brightness = 0 # how bright the LED is
fade_amount = 1285 # 2% steping of 2^16
counter = 0 # counter to keep track of cycles
# blink delay
blink_delay = True
blink_freq_min = 3
blink_freq_max = 6
# Loop forever...
while True:
# turn on LEDs if it is dark out
if photocell.value < darkness_max:
# blink frequency and timer
if blink_delay:
blink_delay = False
blink_timer_start = time.monotonic()
blink_freq = random.randint(blink_freq_min, blink_freq_max)
# time to blink? Blink once every 3 - 6 seconds (random assingment)
if (time.monotonic() - blink_timer_start) >= blink_freq:
blink_delay = True
pwm.duty_cycle = 0
time.sleep(.1)
# send to LED as PWM level
pwm.duty_cycle = brightness
# change the brightness for next time through the loop:
brightness = brightness + fade_amount
# reverse the direction of the fading at the ends of the fade:
if brightness <= 0:
fade_amount = -fade_amount
counter += 1
elif brightness >= 65535:
fade_amount = -fade_amount
counter += 1
# wait for 15 ms to see the dimming effect
time.sleep(.015)
else:
# shutoff LEDs, it is too bright
pwm.duty_cycle = 0
Page last edited March 08, 2024
Text editor powered by tinymce.
Building Your Project
For my project, I went to that staple of American shopping experiences: Walmart. There in the Halloween section is the 97 cent box. I found an assortment of small sippy cups crying to be liberated from their straws and modded to be small, scary light sensitive balls of evil. You may chose to place the eyes into any item or even a board to hide in a window or the bushes.
I placed the photocell and LEDs on the ends of male to female jumper wires. Any hookup wire will do, but the jumper wires hook right in to my breadboard and the components. You will want to trim the component leads a bit before placing in the female end to avoid the wires touching and shorting out.
Put the batteries in the holder and for Trinket plug into the breadboard with the red lead to the BAT+, the black to GND. If you use Gemma, plug the battery holder JST (white) connector into the Gemma battery socket. Lower the board into your selected container and thread the LEDs into the eye holes. If you want, a dab of glue will hold the LEDs in place. Thread the photocell through a hole in the top and secure with tape, Sugru, or other substance being careful not to twist the wires or make them touch.
Turn on the curcuit and put the lid on. Now when it is dark enough, the lights will fade in and out and "blink" (go on and off quickly). Enjoy your scary eyes project.
Low Light Sensitivity
If you find the photocell sensitivity too high or low (which may vary due to the cell's characteristics or the actual value of the 1K resistor), change the value of SENSITIVITY near the top of the code. Valid numbers are zero to 1023, you will want to select a number from about 150 to 800. The lower the number, the darker it must be.Page last edited March 08, 2024
Text editor powered by tinymce.