Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
John Bradnam
Published © GPL3+

NeoPixel Newton Light Cradle

A cool Newton's cradle made from ping pong balls containing WS2812B LEDs.

IntermediateFull instructions provided16 hours1,433

Things used in this project

Hardware components

Microchip ATtiny1614
×1
Linear Regulator (7805)
Linear Regulator (7805)
78M05 DPAK SMD
×1
Resistor 10K 1206 SMD
×1
Resistor 330R 1206 SMD
×1
Capacitor 0.1uF 1206 SMD
×2
Capacitor 470uF 10V Tantalum 7343 SMD
×2
SparkFun RGB LED Breakout - WS2812B
SparkFun RGB LED Breakout - WS2812B
Geekcreit® DC 5V 3MM x 10MM WS2812B SMD LED Board Built-in IC-WS2812
×1
Brass Tube 4.0 x 1000mm 0.45mm Wall
×2
SG90 Micro-servo motor
SG90 Micro-servo motor
×2
Ping Pong Ball
My ping pong balls were 38mm in diameter and were plain (no manufacturers markings)
×5
Potentiometer, Slide
Potentiometer, Slide
45mm length, 36mm travel
×1
Push Button SPDT Momentary Switch
Device: 700-SP7-AP322M1RE Manufacturer: Taiway
×1
DC POWER PANEL PCB 2.1mm Socket Jack
5.5 x 2.1mm
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

STL files

STL files for your slicer for 3D printing

Schematics

Newton Swing Board

Newton Swing Schematic

Newton Base Board

Newton Base Schematic

Eagle Files

Schematics and Boards in Eagle Format

Code

NewtonSwingV1.ino

C/C++
Arduino Sketch
// Newton Swing V1
// jbrad2089@gmail.com
// MPU: ATtiny1614

#include <Adafruit_NeoPixel.h>
#include <Servo_megaTinyCore.h>
#ifdef __AVR__
  #include <avr/power.h>
#endif

/*-----------------------------------------------------------------------------
	ATTiny1614 Pins mapped to Ardunio Pins

             +--------+
         VCC + 1   14 + GND
 (SS)  0 PA4 + 2   13 + PA3 10 (SCK)
       1 PA5 + 3   12 + PA2 9  (MISO)
 (DAC) 2 PA6 + 4   11 + PA1 8  (MOSI)
       3 PA7 + 5   10 + PA0 11 (UPDI)
 (RXD) 4 PB3 + 6    9 + PB0 7  (SCL)
 (TXD) 5 PB2 + 7    8 + PB1 6  (SDA)
             +--------+

  PA0 to PA7 can be analog or digital
  PWM on D0, D1, D6, D7, D10

  BOARD: ATtiny1614/1604/814/804/414/404/214/204
  Chip: ATtiny1614
  Clock Speed: 16MHz
  Programmer: jtag2updi (megaTinyCore)

  Pin mapping: https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/ATtiny_x14.md

-----------------------------------------------------------------------------*/

//Pins
#define SWITCH 2  //(PA6)
#define POT 3 //(PA7)
#define LED 9	//(PA2)
#define L_SERVO 0	//(PA4)
#define R_SERVO 1 //(PA5)

//NeoPixel strip
#define PIXELS 5
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXELS, LED, NEO_GRB + NEO_KHZ800);

//Servos - adjust angles if necessary to ensure servo arm is vertical
#define SERVO_L_OFS 90
#define SERVO_R_OFS 90
Servo servoL;
Servo servoR;

enum ModeEnum { SWITCH_OFF, RAINBOW, SWING, EXPLODE, END_OF_LIST };

#define MIN_DELAY 60
#define STEP_ANGLE 5
#define SERVO_DELAY 20

bool enableServos = false;
bool switchPressed = false;
int delta = 100;
ModeEnum currentMode = SWITCH_OFF;

//-------------------------------------------------------------------------
//Initialise Hardware

void setup() 
{
  pinMode(POT, INPUT);
  pinMode(SWITCH, INPUT);
  pinMode(LED, OUTPUT);
  pinMode(L_SERVO, OUTPUT);
  pinMode(R_SERVO, OUTPUT);

  strip.begin();
  strip.setBrightness(255);
  strip.show(); // Initialize all pixels to 'off'

  //Set up servos
  servoL.attach(L_SERVO);
  servoL.write(SERVO_L_OFS);
  servoR.attach(R_SERVO);
  servoR.write(SERVO_R_OFS);
}

//-------------------------------------------------------------------------
// Handle interactions

void loop() 
{

  switch (currentMode)
  {
    case SWITCH_OFF:  delay(200); break;
    case RAINBOW:     lightRainbow(delta);  break;
    case SWING:       newtonSwing(delta); break;
    case EXPLODE:     newtonExplode(delta); break;
  }

  getUserInputs();
  if (switchPressed)
  {
    switchPressed = false;
    currentMode = (ModeEnum)(((int)currentMode) + 1);
    if (currentMode == END_OF_LIST)
    {
      currentMode = SWITCH_OFF;
      //Only enable servos after user has gone through all modes.
      //This allows light only modes for Newton Swing and Newton Explode modes
      enableServos = true;
    }
    //Turn off all LEDs
    colorWipe(0, 1);
  }
}

//-------------------------------------------------------------------------
// Perform a standard Newton Swing passing light to each ball;
// wait - time between frames

void newtonSwing(int wait)
{
  
  for (int a = 0; a < 255; a += 8)
  {
    uint32_t color = Wheel(a);
    
    getUserInputs();
    if (switchPressed)
    {
      break;
    }
    
    //LED 0
    strip.setPixelColor(4, 0);
    strip.setPixelColor(0, color);
    strip.show();
  
    if (enableServos)
    {
      //Swing left servo out and back
      for (int angle = 0; angle <= 90; angle += STEP_ANGLE)
      {
        servoL.write(SERVO_L_OFS - ((angle > 45) ? 90 - angle : angle));
        delay(SERVO_DELAY);
      }
    }
    else
    {
      delay(wait);
    }
  
    //LED 1 - 3
    for (int i = 1; i <= 3; i++)
    {
      strip.setPixelColor(i-1, 0);
      strip.setPixelColor(i, color);
      strip.show();
      delay(wait);
    }
    
    //LED 5
    strip.setPixelColor(3, 0);
    strip.setPixelColor(4, color);
    strip.show();
    
    if (enableServos)
    {
      //Swing right servo out and back
      for (int angle = 0; angle <= 90; angle += STEP_ANGLE)
      {
        servoR.write(SERVO_R_OFS + ((angle > 45) ? 90 - angle : angle));
        delay(SERVO_DELAY);
      }
    }
    else
    {
      delay(wait);
    }
  
    //LED 3 - 1
    for (int i = 3; i >= 1; i--)
    {
      strip.setPixelColor(i+1, 0);
      strip.setPixelColor(i, color);
      strip.show();
      delay(wait);
    }
  }
}

//-------------------------------------------------------------------------
// Perform a standard Newton Swing passing light to each ball;
// wait - time between frames

void newtonExplode(int wait)
{
  for (int a = 0; a < 255; a += 8)
  {
    uint32_t color = Wheel(a);
    
    getUserInputs();
    if (switchPressed)
    {
      break;
    }
    
    //LED 0
    strip.setPixelColor(3, 0);
    strip.setPixelColor(1, 0);
    strip.setPixelColor(4, color);
    strip.setPixelColor(0, color);
    strip.show();
    
    if (enableServos)
    {
      //Swing left and right servos out and back
      for (int angle = 0; angle <= 90; angle += STEP_ANGLE)
      {
        servoL.write(SERVO_L_OFS - ((angle > 45) ? 90 - angle : angle));
        servoR.write(SERVO_R_OFS + ((angle > 45) ? 90 - angle : angle));
        delay(SERVO_DELAY);
      }
    }
    else
    {
      delay(wait);
    }
  
    //LEDs 1 and 3
    strip.setPixelColor(4, 0);
    strip.setPixelColor(0, 0);
    strip.setPixelColor(3, color);
    strip.setPixelColor(1, color);
    strip.show();
    delay(wait);
  
    //LED 2
    strip.setPixelColor(3, 0);
    strip.setPixelColor(1, 0);
    strip.setPixelColor(2, color);
    strip.show();
    delay(wait);
    
    //LED 1 & 3
    strip.setPixelColor(2, 0);
    strip.setPixelColor(1, color);
    strip.setPixelColor(3, color);
    strip.show();
    delay(wait);
  }
}

//-------------------------------------------------------------------------
// Cycle each LED through the rainbow
// wait - time between frames

void lightRainbow(int wait)
{
  int gap = floor(255 / strip.numPixels()); 
  for (int c = 0; c < 256; c++)
  {
    for (int i = 0; i < strip.numPixels(); i++)
    {
      strip.setPixelColor(i, Wheel((c + gap * i) & 0xff));
    }
    strip.show();
    getUserInputs();
    if (switchPressed)
    {
      break;
    }
    delay(wait);
  }
  //Turn off all LEDs
  colorWipe(0, 1);
}

//-------------------------------------------------------------------------
// Read the pot and set the delta time between frames and read switch
// returns true if switch is pressed

bool getUserInputs()
{
  int pot = 100 - map(analogRead(POT), 0, 1023, 0, 100);
  delta = MIN_DELAY + pot * 2;

  if (digitalRead(SWITCH) == LOW )
  { 
    // Debounce by waiting then checking again
    delay(10);
    if (digitalRead(SWITCH) == LOW)
    {
      //Wait until button is released
      while (digitalRead(SWITCH) == LOW)
        ;
      switchPressed = true;
    }
  }
  return switchPressed;
}
 
//-------------------------------------------------------------------------
// Fill the dots one after the other with a color
// c - color to set
// wait - time delay between setting each LED in the Neopixel strip

void colorWipe(uint32_t c, uint8_t wait) 
{
  for(uint16_t i=0; i<strip.numPixels(); i++) 
  {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

//-------------------------------------------------------------------------
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.

uint32_t Wheel(byte WheelPos) 
{
  WheelPos = 255 - WheelPos;
  if(WheelPos < 85) 
  {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if(WheelPos < 170) 
  {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

Credits

John Bradnam

John Bradnam

147 projects • 181 followers
Thanks to TecnoProfesor.

Comments