Things used in this project

Schematics

Pi Quilt Neopixels and Arduino Clock
Lighting up neopixels to tell time!
Schem s6mmqqhjpt

Code

PiClockArduino
For use with a fabric pi quilt & neopixels!
Created by Christopher & Jessica Hogan 2017

#include <Time.h>
#include <TimeLib.h>
#include <FastLED.h>
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif

#define PI_PIN            8
#define NUM_PI_PIXELS    13
#define CLOCK_PIN        10
#define NUM_CLOCK_PIXELS 66
#define LED_PIN     10
#define NUM_LEDS    66
#define BRIGHTNESS  64
#define LED_TYPE    WS2811
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];

#define UPDATES_PER_SECOND 100
// 3.14159.... but in reverse because the neopixels are wired in the
// opposite way, and they only work in one direction. The array could
// be specified in the regular order if the neopixels had been soldered
// in the reverse direction.
int piDigits[] = {
  0, 3, 2, 9, 5, 4, 4, 9, 4, 7, 9, 0, 2, 8, 5,
  0, 1, 5, 7, 3, 9, 9, 3, 9, 6, 1, 7, 9, 1, 4,
  8, 8, 2, 0, 5, 9, 7, 2, 3, 8, 3, 3, 4, 6, 2,
  6, 4, 8, 3, 2, 3, 9, 7, 9, 8, 5, 3, 5, 6, 2,
  9, 5, 1, 4, 1, 3
};

// This is how we clear the digits that aren't being lit.
// Whenever a digit is lit, the corresponding index into piDigits
// is stored in the array according to the scheme hh:mm for indices
// 0,1,2,3.  So, if the time were 2:36, litPixels would have the
// value [-1, 59, 1, 24].  The hour is indexed starting at the end
// of piDigits, and the minute is indexed starting at the beginning.
// A -1 indicates that nothing in that position needs cleared. For
// the example, the first hour digit is not used, so -1. The next
// hour digit is 2, and the first instance of 2 from the end of the
// piDigits array is at index 59. The minutes start at the beginning
// of the array. The first instance of 3 is at index 1, and the first
// instance of 6 is at index 24.
int litPixels[4] = { -1, -1, -1, -1};

// The pixels that comprise the pi symbol
Adafruit_NeoPixel piPixels = Adafruit_NeoPixel(NUM_PI_PIXELS, PI_PIN, NEO_GRB + NEO_KHZ800);
// The pixels that comprise all the numbers
Adafruit_NeoPixel clockPixels = Adafruit_NeoPixel(NUM_CLOCK_PIXELS, CLOCK_PIN,  NEO_GRB + NEO_KHZ800);

// We need to keep track of the previous minute, second, and
// hour so we know when they have changed.  Initialize them
// all to -1 so the clock starts working right away.
int previousSecond = -1;
int previousMinute = -1;
int previousHour = -1;

// This variable keeps track of even seconds becuase we blink
// the pi symbol every other (even) second.
bool even = true;

extern CRGBPalette16 myRedWhiteBluePalette;
extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;

CRGBPalette16 currentPalette;
TBlendType    currentBlending;

void setup() {
  delay( 3000 ); // power-up safety delay
  // Serial.begin(9600);
  int hour = 6;
  int minute = 23;
  int second = 0;
  int day = 9;
  int month = 3;
  int year = 2017;
  setTime(hour, minute, second, day, month, year);
  piPixels.begin();
  clockPixels.begin();

  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
  FastLED.setBrightness(  BRIGHTNESS );
    
  currentPalette = RainbowColors_p;
  currentBlending = LINEARBLEND;
}

void loop() {
  /////////////
  // Pi code //
  /////////////

  int currentSecond = second();
  if (previousSecond != currentSecond) {
    even = !even;
    if (even) {
      int r = random(0, 256);
      int g = random(0, 256);
      int b = random(0, 256);
      for (int i = 0; i < NUM_PI_PIXELS; i++) {
        piPixels.setPixelColor(i, piPixels.Color(r, g, b));
        piPixels.show();
      }
    }
    else {
      for (int i = 0; i < NUM_PI_PIXELS; i++) {
        piPixels.setPixelColor(i, 0);
        piPixels.show();
      }
    }
    previousSecond = currentSecond;
  }

  //////////////////
  // Clock Digits //
  //////////////////

  int currentHour = hourFormat12();
  int currentMinute = minute();

  if(currentHour == 3 && currentMinute == 14) {
    // Run crazy pi colors for 1 minute
    piTime();
    return;
  }
  // Handle hour  
  if (currentHour != previousHour) {
    if (currentHour >= 1 && currentHour <= 9) {
      // 1 digit -> Orange
      int hourPixelOnes = -1;
      for (int i = NUM_CLOCK_PIXELS; i > 0; i--) {
        if (piDigits[i] == currentHour) {
          hourPixelOnes = i;
          break;
        }
      }
      clearLitHour();
      clockPixels.setPixelColor(hourPixelOnes, clockPixels.Color(225, 34, 4));
      litPixels[0] = -1;
      litPixels[1] = hourPixelOnes;
      clockPixels.show();
    }
    else { // 2 digits
      // Red Yellow
      int tensHourPixel = 64;
      int onesHourPixel;
      if(currentHour == 10) {
        onesHourPixel = 33;
      } else if(currentHour == 11) {
        onesHourPixel = 62;
      } else { // 12
        onesHourPixel = 59;
      }

      clearLitHour();

      // Red
      clockPixels.setPixelColor(tensHourPixel, clockPixels.Color(255, 0, 0 ));
      // Yellow
      clockPixels.setPixelColor(onesHourPixel, clockPixels.Color(190, 190, 0 ));

      // Remember which pixel we lit so we can turn it off later
      litPixels[0] = tensHourPixel;
      litPixels[1] = onesHourPixel;
     
      clockPixels.show();
    }
    previousHour = currentHour;
  }

  // Handle minute
  if (currentMinute != previousMinute) {
    if (currentMinute == 0) {
      // Exactly on the hour, so light no minutes
      clearLitMinute();
      litPixels[2] = -1;
      litPixels[3] = -1;
      clockPixels.show();
    }
    else if (currentMinute >= 1 && currentMinute <= 9) {
      // 1 digit -> Yellow
      int pixelToLight = -1;
      for (int i = 0; i < NUM_CLOCK_PIXELS; i++) {
        if (piDigits[i] == currentMinute) {
          pixelToLight = i;
          break;
        }
      }
      clearLitMinute();
      litPixels[2] = -1;

      if (currentHour > 9) {
        // Green
        clockPixels.setPixelColor(pixelToLight, clockPixels.Color(0, 150, 0 ));
      }
      else {
        // Yellow
        clockPixels.setPixelColor(pixelToLight, clockPixels.Color(190, 190, 0 ));
      }
      // Remember which pixel we lit so we can turn it off later
      litPixels[3] = pixelToLight;
      clockPixels.show();
    }
    else { // Minute is 2 digits

      int tensMinutePixel = -1;
      int onesMinutePixel = -1;

      String minuteString = String(currentMinute);
      const char tensChar = minuteString[0];
      const char onesChar = minuteString[1];
      tensMinutePixel = -1;
      onesMinutePixel = -1;

      onesMinutePixel = findIndexOf(onesChar);

      if (tensChar == onesChar) {
        for (int i = onesMinutePixel + 1; i < NUM_CLOCK_PIXELS; i++) {
          if (piDigits[i] == tensChar - '0') {
              tensMinutePixel = i;
              break;
          }
        }
      }
      else {
        tensMinutePixel = findIndexOf(tensChar);
      }

      clearLitMinute();
      
      if (currentHour < 10) {
        // turquoise
        clockPixels.setPixelColor(tensMinutePixel, clockPixels.Color(0, 174, 255 ));
        // purple
        clockPixels.setPixelColor(onesMinutePixel, clockPixels.Color(135, 0, 255 ));
      }
      else {
        // Green
        clockPixels.setPixelColor(tensMinutePixel, clockPixels.Color(0, 255, 0 ));
        // Blue
        clockPixels.setPixelColor(onesMinutePixel, clockPixels.Color(0, 0, 255 ));
      }

      // Remember which pixel we lit so we can turn it off later
      litPixels[2] = tensMinutePixel;
      litPixels[3] = onesMinutePixel;

      clockPixels.show();
    }
    previousMinute = currentMinute;
  }
}

int findIndexOf(char digit) {
  for (int i = 0; i < NUM_CLOCK_PIXELS; i++) {
    if (piDigits[i] == digit - '0') {
      return i;
    }
  }
  return -1;
}

int findReverseIndexOf(char digit) {
  for (int i = NUM_CLOCK_PIXELS - 1; i >= 0; i--) {
    if (piDigits[i] == digit - '0') {
      return i;
    }
  }
  return -1;
}

void clearLitMinute() {
  if (litPixels[2] != -1) {
    clockPixels.setPixelColor(litPixels[2], 0);
  }
  if (litPixels[3] != -1) {
    clockPixels.setPixelColor(litPixels[3], 0);
  }
}

void clearLitHour() {
  if (litPixels[0] != -1) {
    clockPixels.setPixelColor(litPixels[0], 0);
  }
  if (litPixels[1] != -1) {
    clockPixels.setPixelColor(litPixels[1], 0);
  }
}

void piTime() {
    int keepRunning = 5;
    while(keepRunning > 0) {
      ChangePalettePeriodically();
      
      static uint8_t startIndex = 0;
      startIndex = startIndex + 1; /* motion speed */
      
      FillLEDsFromPaletteColors( startIndex);
      
      FastLED.show();
      FastLED.delay(1000 / UPDATES_PER_SECOND);
      keepRunning--;
    }
}

void FillLEDsFromPaletteColors( uint8_t colorIndex)
{
    uint8_t brightness = 255;
    
    for( int i = 0; i < NUM_LEDS; i++) {
        leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
        colorIndex += 3;
    }
}


// There are several different palettes of colors demonstrated here.
//
// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p,
// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p.
//
// Additionally, you can manually define your own color palettes, or you can write
// code that creates color palettes on the fly.  All are shown here.

void ChangePalettePeriodically()
{
    uint8_t secondHand = (millis() / 1000) % 60;
    static uint8_t lastSecond = 99;
    
    if( lastSecond != secondHand) {
        lastSecond = secondHand;
        if( secondHand ==  0)  { currentPalette = RainbowColors_p;         currentBlending = LINEARBLEND; }
        if( secondHand == 10)  { currentPalette = RainbowStripeColors_p;   currentBlending = NOBLEND;  }
        if( secondHand == 15)  { currentPalette = RainbowStripeColors_p;   currentBlending = LINEARBLEND; }
        if( secondHand == 20)  { SetupPurpleAndGreenPalette();             currentBlending = LINEARBLEND; }
        if( secondHand == 25)  { SetupTotallyRandomPalette();              currentBlending = LINEARBLEND; }
        if( secondHand == 30)  { SetupBlackAndWhiteStripedPalette();       currentBlending = NOBLEND; }
        if( secondHand == 35)  { SetupBlackAndWhiteStripedPalette();       currentBlending = LINEARBLEND; }
        if( secondHand == 40)  { currentPalette = CloudColors_p;           currentBlending = LINEARBLEND; }
        if( secondHand == 45)  { currentPalette = PartyColors_p;           currentBlending = LINEARBLEND; }
        if( secondHand == 50)  { currentPalette = myRedWhiteBluePalette_p; currentBlending = NOBLEND;  }
        if( secondHand == 55)  { currentPalette = myRedWhiteBluePalette_p; currentBlending = LINEARBLEND; }
    }
}

// This function fills the palette with totally random colors.
void SetupTotallyRandomPalette()
{
    for( int i = 0; i < 16; i++) {
        currentPalette[i] = CHSV( random8(), 255, random8());
    }
}

// This function sets up a palette of black and white stripes,
// using code.  Since the palette is effectively an array of
// sixteen CRGB colors, the various fill_* functions can be used
// to set them up.
void SetupBlackAndWhiteStripedPalette()
{
    // 'black out' all 16 palette entries...
    fill_solid( currentPalette, 16, CRGB::Black);
    // and set every fourth one to white.
    currentPalette[0] = CRGB::White;
    currentPalette[4] = CRGB::White;
    currentPalette[8] = CRGB::White;
    currentPalette[12] = CRGB::White;
    
}

// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette()
{
    CRGB purple = CHSV( HUE_PURPLE, 255, 255);
    CRGB green  = CHSV( HUE_GREEN, 255, 255);
    CRGB black  = CRGB::Black;
    
    currentPalette = CRGBPalette16(
                                   green,  green,  black,  black,
                                   purple, purple, black,  black,
                                   green,  green,  black,  black,
                                   purple, purple, black,  black );
}


// This example shows how to set up a static color palette
// which is stored in PROGMEM (flash), which is almost always more
// plentiful than RAM.  A static PROGMEM palette like this
// takes up 64 bytes of flash.
const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM =
{
    CRGB::Red,
    CRGB::Gray, // 'white' is too bright compared to red and blue
    CRGB::Blue,
    CRGB::Black,
    
    CRGB::Red,
    CRGB::Gray,
    CRGB::Blue,
    CRGB::Black,
    
    CRGB::Red,
    CRGB::Red,
    CRGB::Gray,
    CRGB::Gray,
    CRGB::Blue,
    CRGB::Blue,
    CRGB::Black,
    CRGB::Black
};

Credits

Thanks to Arduino, Adafruit, and Pale Gray Labs.

Replications

Did you replicate this project? Share it!

I made one

Love this project? Think it could be improved? Tell us what you think!

Give feedback

Comments

Similar projects you might like

Pen Stand: Gaming Enabled
Intermediate
  • 58
  • 4

Full instructions

Do you have a pen stand that just sits there and does nothing? Can we make it play a game/flash LEDs every time you use it? Let's find out!

Arduino on Internal Oscillator Crystal as Clock Source
Intermediate
  • 1,064
  • 74

Protip

Want to make an Arduino having only an ATmega328 chip and nothing else? Not even the crystal oscillator!

Self Balancing Robot Using Mpu6050 Accelerometer
Intermediate
  • 240
  • 8

Protip

this is self balancing robot on two wheels using mpu6050 accelerometer based on arduino..

Dancing Autobot
Intermediate
  • 173
  • 3

Protip

this is our project..and new thats called autobot with dancing like michael jackson....

Barbot: Cocktail Mixing Robot
Intermediate
  • 5,695
  • 42

Full instructions

Barbot is an open source Arduino cocktail mixing robot controlled with the hybrid mobile app via Bluetooth.

Distance Measurement Vehicle via Websocket
Intermediate
  • 4,981
  • 35

Full instructions

A vehicle measures distance with an encoder on its wheel. It is remotely controlled and transmits the distance via Websocket.

Sign up / LoginProjectsCommunitiesTopicsContestsLiveAppsBetaFree StoreBlog