Project showcase

Servo Controlled Word Clock © CC BY-NC-SA

This variation of the popular word clock is controlled by 114 servo motors.

  • 278 views
  • 1 comment
  • 2 respects

Components and supplies

Sg90 servo motor 180 degrees sg90 micro
SG90 Micro-servo motor
×114
Adafruit 16-Channel 12-bit PWM/Servo Driver - I2C interface - PCA9685
×8
5 m WS2812b LED strip, 60 LEDs/m
×1
DS3231 RTC module
×1
Ard nano
Arduino Nano R3
×1
VS1838B IR receiver
×1
5 V, 10 A power supply
×1
08377 02 l
Resistor 330 ohm
×1
Panasonic eca 1em102
Capacitor 1000 µF
×1
15 cm servo extension cable
×20
cable DC socket to bare wire
×1

About this project

For a complete description of the project please watch the making of video below.

Code

main code for word clockArduino
main code for the arduino nano controlling the clock
/*************************************************** 
main code for servo controlled word clock project  
Mar-2019
Moritz v. Sivers
****************************************************/

#include <FastLED.h>
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#include <DS1307RTC.h>
#include <Time.h>
#include <TimeLib.h> 
#include <IRremote.h>
#include <avr/pgmspace.h>

// setup servo drivers
Adafruit_PWMServoDriver pwm1 = Adafruit_PWMServoDriver(0x40);
Adafruit_PWMServoDriver pwm2 = Adafruit_PWMServoDriver(0x41);
Adafruit_PWMServoDriver pwm3 = Adafruit_PWMServoDriver(0x42);
Adafruit_PWMServoDriver pwm4 = Adafruit_PWMServoDriver(0x43);
Adafruit_PWMServoDriver pwm5 = Adafruit_PWMServoDriver(0x44);
Adafruit_PWMServoDriver pwm6 = Adafruit_PWMServoDriver(0x45);
Adafruit_PWMServoDriver pwm7 = Adafruit_PWMServoDriver(0x46);
Adafruit_PWMServoDriver pwm8 = Adafruit_PWMServoDriver(0x47);


// calibrated min/max positions for every servo
//
const uint16_t SERVOMIN_CAL[] PROGMEM = {   230,  210,  230,  230,  240,  260,  240,  240,  230,  230,  230,
                            270,  230,  220,  260,  250,  310,  230,  240,  250,  220,  230,
                            200,  250,  250,  240,  240,  240,  210,  220,  220,  240,  230,
                            270,  250,  240,  230,  220,  240,  270,  240,  240,  230,  260,
                            270,  220,  250,  250,  280,  250,  230,  230,  210,  290,  260,
                            230,  200,  210,  220,  240,  260,  240,  280,  260,  280,  240,
                            230,  220,  240,  240,  210,  220,  260,  250,  260,  260,  280,
                            230,  230,  260,  280,  250,  240,  240,  270,  220,  260,  250,
                            280,  250,  280,  230,  270,  270,  290,  250,  240,  290,  250,
                            240,  260,  250,  210,  230,  240,  250,  260,  280,  220,  260,
                            220,  240,  230,  240 };

const uint16_t SERVOMAX_CAL[] PROGMEM = {   490,  470,  460,  490,  490,  510,  450,  420,  470,  480,  450,
                            500,  480,  430,  510,  490,  550,  450,  460,  470,  470,  480,
                            450,  500,  500,  480,  470,  450,  400,  420,  440,  470,  460,
                            480,  470,  450,  430,  420,  440,  450,  420,  450,  460,  490,
                            490,  440,  440,  460,  470,  420,  410,  410,  400,  530,  490,
                            450,  340,  430,  415,  440,  430,  450,  490,  450,  500,  480,   
                            460,  450,  450,  450,  420,  410,  460,  460,  460,  490,  520,
                            460,  460,  460,  490,  450,  460,  460,  500,  450,  490,  500,
                            520,  470,  490,  440,  470,  470,  490,  480,  500,  540,  500,  
                            480,  500,  480,  460,  470,  480,  520,  510,  520,  490,  530, 
                            480,  510,  500,  520};
                            
// current servo position
uint16_t currentPos[114];


// definition of words (row,column), rows and columns start counting from 1 in upper left corner
//
uint8_t IT[][2] = { {1,1}, {1,2} };
uint8_t IS[][2] = { {1,4}, {1,5} };
uint8_t QUARTER[][2] = { {2,3}, {2,4}, {2,5}, {2,6}, {2,7}, {2,8}, {2,9} };
uint8_t TWENTY[][2] = { {3,1}, {3,2}, {3,3}, {3,4}, {3,5}, {3,6} };
uint8_t FIVE_M[][2] = { {3,7}, {3,8}, {3,9}, {3,10} };
uint8_t HALF[][2] = { {4,1}, {4,2}, {4,3}, {4,4} };
uint8_t TEN_M[][2] = { {4,6}, {4,7}, {4,8} };
uint8_t TO[][2] = { {4,10}, {4,11} };
uint8_t PAST[][2] = { {5,1}, {5,2}, {5,3}, {5,4} };
uint8_t NINE[][2] = { {5,8}, {5,9}, {5,10}, {5,11} };
uint8_t ONE[][2] = { {6,1}, {6,2}, {6,3} };
uint8_t SIX[][2] = { {6,4}, {6,5}, {6,6} };
uint8_t THREE[][2] = { {6,7}, {6,8}, {6,9}, {6,10}, {6,11} };
uint8_t FOUR[][2] = { {7,1}, {7,2}, {7,3}, {7,4} };
uint8_t FIVE[][2] = { {7,5}, {7,6}, {7,7}, {7,8} };
uint8_t TWO[][2] = { {7,9}, {7,10}, {7,11} };
uint8_t EIGHT[][2] = { {8,1}, {8,2}, {8,3}, {8,4}, {8,5} };
uint8_t ELEVEN[][2] = { {8,6}, {8,7}, {8,8}, {8,9}, {8,10}, {8,11} };
uint8_t SEVEN[][2] = { {9,1}, {9,2}, {9,3}, {9,4}, {9,5} };
uint8_t TWELVE[][2] = { {9,6}, {9,7}, {9,8}, {9,9}, {9,10}, {9,11} };
uint8_t TEN[][2] = { {10,1}, {10,2}, {10,3} };
uint8_t OCLOCK[][2] = { {10,6}, {10,7}, {10,8}, {10,9}, {10,10}, {10,11} };

// minute of last display update
uint8_t lastmin;

// milliseconds of last update
unsigned long lastMillis;

// structure for current time
time_t c_time; 

// our servo # counter
//uint8_t servonum = 0;

// PIN for IR receiver
#define PIN_IR 2

// default DS3231 I2C address
#define DS3231_ADDRESS 0x68

// master brightness control
#define BRIGHTNESS    255

// How many leds in your strip?
#define NUM_LEDS 114

// For led chips like Neopixels, which have a data line, ground, and power, you just
// need to define DATA_PIN.  For led chipsets that are SPI based (four wires - data, clock,
// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN
#define DATA_PIN 9
//#define CLOCK_PIN 13

// Define the array of leds
CRGB leds[NUM_LEDS];

// color of background LEDs
uint8_t gHue = random(0,255);

// color of dots
uint8_t hueDots = random(0,255);

// global on/off status
uint8_t gStatus = 0;

// define IR receiver variables
IRrecv irrecv(PIN_IR);
decode_results results;

 
void setup() {
  //Serial.begin(9600);
  
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(BRIGHTNESS);
  fill_solid(leds,NUM_LEDS,CRGB::Black);
  FastLED.show();

  irrecv.enableIRIn();

  pwm1.begin();
  pwm1.setPWMFreq(60);  // Analog servos run at ~60 Hz updates
  pwm2.begin();
  pwm2.setPWMFreq(60);  
  pwm3.begin();
  pwm3.setPWMFreq(60);  
  pwm4.begin();
  pwm4.setPWMFreq(60);  
  pwm5.begin();
  pwm5.setPWMFreq(60);  
  pwm6.begin();
  pwm6.setPWMFreq(60);  
  pwm7.begin();
  pwm7.setPWMFreq(60);  
  pwm8.begin();
  pwm8.setPWMFreq(60); 

  initMatrix();

  delay(1000);

  lastmin = minute(c_time); // initialize last update of display
  lastMillis = millis();

}


void loop() {

  // readout time and check for pressed buttons
  c_time = RTC.get();
  readIR();

  // if clock is on
  if(gStatus==1) {
    
    // update words every 5 minutes
    if( ((minute(c_time) % 5) == 0 && minute(c_time) > lastmin) || (minute(c_time) == 0 && lastmin == 59)) {
      if(minute(c_time)==0) randomMove(); // make random move every full hour
      updateTime(random(1,3));
      lastmin = minute(c_time);
    }
    
    // update dots every minute
    if((minute(c_time) > lastmin) || (minute(c_time) == 0 && lastmin == 59)) {
     updateMinutes(1);
     lastmin = minute(c_time);
    }

    // slowly move dot for seconds forward
    //updateSeconds();
  }
  
}


// shows time on clock
// effect = 0: no effect when seting time, effect = 1: blend in effect, effect = 2: typing effect
void updateTime(int effect) {

  // reset all letters
  resetLetters();
  
   // light up "IT IS" first
    lightup(IT,sizeof(IT)/sizeof(IT[0]),effect);
    lightup(IS,sizeof(IS)/sizeof(IS[0]),effect);

  // show minutes and hours
  switch (minute(c_time) / 5) {
                case 0:
                    // full hour
                      setHours(hour(c_time),effect);
                      lightup(OCLOCK,sizeof(OCLOCK)/sizeof(OCLOCK[0]),effect);
                    break;
                case 1:
                    // 5 past
                      lightup(FIVE_M,sizeof(FIVE_M)/sizeof(FIVE_M[0]),effect);
                      lightup(PAST,sizeof(PAST)/sizeof(PAST[0]),effect);
                      setHours(hour(c_time),effect);
                    break;
                case 2:
                    // 10 past
                      lightup(TEN_M,sizeof(TEN_M)/sizeof(TEN_M[0]),effect);
                      lightup(PAST,sizeof(PAST)/sizeof(PAST[0]),effect);
                      setHours(hour(c_time),effect);
                    break;
                case 3:
                    // quarter past
                      lightup(QUARTER,sizeof(QUARTER)/sizeof(QUARTER[0]),effect);
                      lightup(PAST,sizeof(PAST)/sizeof(PAST[0]),effect);
                      setHours(hour(c_time),effect);
                    break;
                case 4:
                    // 20 past
                    lightup(TWENTY,sizeof(TWENTY)/sizeof(TWENTY[0]),effect);
                      lightup(PAST,sizeof(PAST)/sizeof(PAST[0]),effect);
                      setHours(hour(c_time),effect);
                    break;
                case 5:
                    // 25 past
                 lightup(TWENTY,sizeof(TWENTY)/sizeof(TWENTY[0]),effect);
                      lightup(FIVE_M,sizeof(FIVE_M)/sizeof(FIVE_M[0]),effect);
                      lightup(PAST,sizeof(PAST)/sizeof(PAST[0]),effect);
                      setHours(hour(c_time),effect);
                    break;
                case 6:
                    // half past
                    
                      lightup(HALF,sizeof(HALF)/sizeof(HALF[0]),effect);
                      lightup(PAST,sizeof(PAST)/sizeof(PAST[0]),effect);
                      setHours(hour(c_time),effect);
                    
                    break;
                case 7:
                    // 25 to
                    
                      lightup(TWENTY,sizeof(TWENTY)/sizeof(TWENTY[0]),effect);
                      lightup(FIVE_M,sizeof(FIVE_M)/sizeof(FIVE_M[0]),effect);
                      lightup(TO,sizeof(TO)/sizeof(TO[0]),effect);
                      setHours(hour(c_time) + 1,effect);
                    
                    break;
                case 8:
                    // 20 to
                    
                      lightup(TWENTY,sizeof(TWENTY)/sizeof(TWENTY[0]),effect);
                      lightup(TO,sizeof(TO)/sizeof(TO[0]),effect);
                      setHours(hour(c_time) + 1,effect);
                     
                    break;
                case 9:
                    // 15 to
                    
                      lightup(QUARTER,sizeof(QUARTER)/sizeof(QUARTER[0]),effect);
                      lightup(TO,sizeof(TO)/sizeof(TO[0]),effect);
                      setHours(hour(c_time) + 1,effect);
                    
                    break;
                case 10:
                    
                      lightup(TEN_M,sizeof(TEN_M)/sizeof(TEN_M[0]),effect);
                      lightup(TO,sizeof(TO)/sizeof(TO[0]),effect);
                      setHours(hour(c_time) + 1,effect);
                    
                    break;
                case 11:
                    // 5 to
                    
                      lightup(FIVE_M,sizeof(FIVE_M)/sizeof(FIVE_M[0]),effect);
                      lightup(TO,sizeof(TO)/sizeof(TO[0]),effect);
                      setHours(hour(c_time) + 1,effect);
                    
                    break;
            }
    updateMinutes(effect);

  // print time to serial monitor
  //printTime();
}

// show hours on display
//
void setHours(byte c_hour, int effect) {
    switch (c_hour) {
                case 0:
                case 12:
                case 24:
                    lightup(TWELVE,sizeof(TWELVE)/sizeof(TWELVE[0]),effect);
                    break;
                case 1:
                case 13:
                    lightup(ONE,sizeof(ONE)/sizeof(ONE[0]),effect);
                    break;
                case 2:
                case 14:
                   lightup(TWO,sizeof(TWO)/sizeof(TWO[0]),effect);
                    break;
                case 3:
                case 15:
                    lightup(THREE,sizeof(THREE)/sizeof(THREE[0]),effect);
                    break;
                case 4:
                case 16:
                    lightup(FOUR,sizeof(FOUR)/sizeof(FOUR[0]),effect);
                    break;
                case 5:
                case 17:
                    lightup(FIVE,sizeof(FIVE)/sizeof(FIVE[0]),effect);
                    break;
                case 6:
                case 18:
                    lightup(SIX,sizeof(SIX)/sizeof(SIX[0]),effect);
                    break;
                case 7:
                case 19:
                    lightup(SEVEN,sizeof(SEVEN)/sizeof(SEVEN[0]),effect);
                    break;
                case 8:
                case 20:
                    lightup(EIGHT,sizeof(EIGHT)/sizeof(EIGHT[0]),effect);
                    break;
                case 9:
                case 21:
                    lightup(NINE,sizeof(NINE)/sizeof(NINE[0]),effect);
                    break;
                case 10:
                case 22:
                    lightup(TEN,sizeof(TEN)/sizeof(TEN[0]),effect);
                    break;
                case 11:
                case 23:
                    lightup(ELEVEN,sizeof(ELEVEN)/sizeof(ELEVEN[0]),effect);
                    break;
            }            
}

// show word on display
//
void lightup(uint8_t Word[][2], int nLetters, int effect) {
  int hue = random(0,255); //choose random color for word 
  if (effect==2) {    // typing effect (letters appear from left to right)
    for (int i = 0; i < nLetters; i++) {
      unsigned int servomax = pgm_read_word_near(SERVOMAX_CAL + servonum(Word[i][0],Word[i][1]));
      moveServo(Word[i][0],Word[i][1],servomax);
      lightLED(Word[i][0],Word[i][1],hue,255,255);
      FastLED.show();
      delay(150);
    }
  }
  else if (effect==1) { // words move slowly in, all letters simultaneously
    int nSteps[nLetters];         // number of servo steps from current position to front
    double hueStep[nLetters];     // number of hue steps per servo step
    double currentHue[nLetters];  // current color
    int finished[nLetters];       // set to 1 if servo is at final position
    
    for (int i = 0; i < nLetters; i++) {
      unsigned int servomax = pgm_read_word_near(SERVOMAX_CAL + servonum(Word[i][0],Word[i][1]));
      nSteps[i] = servomax - currentPos[servonum(Word[i][0],Word[i][1])];   
      hueStep[i] = (double)(hue - gHue)/(double)(nSteps[i]);    
      currentHue[i] = (double)gHue; 

      //Serial.print(F("nSteps: "));
      //Serial.println(nSteps[i]);
      //Serial.print(F("hueStep: "));
      //Serial.println(hueStep[i]);
    
    }
    int n = 0;  // count letters in final position
    while (n<nLetters) {
      n = 0;
      for (int i = 0; i < nLetters; i++) {
        unsigned int pos = currentPos[servonum(Word[i][0],Word[i][1])];
        unsigned int servomax = pgm_read_word_near(SERVOMAX_CAL + servonum(Word[i][0],Word[i][1]));
        //Serial.print(F("current pos: "));
        //Serial.println(pos);
        if(pos<servomax) {
          pos++;
          moveServo(Word[i][0],Word[i][1],pos);
          currentHue[i] += hueStep[i];
          lightLED(Word[i][0],Word[i][1],(int)currentHue[i],255,255);
          //Serial.print(F("new pos: "));
          //Serial.println(pos);
          //Serial.print(F("new hue: "));
          //Serial.println(currentHue[i]);
        }
        else {
          //Serial.println(F("finished"));
          n++;
        }
      }
      FastLED.show();
      delay(5);
    }
   }
    else {                // time setting mode (just light up words do not move servos)
      for (int i = 0; i < nLetters; i++) {
        lightLED(Word[i][0],Word[i][1],hue,255,255);
        FastLED.show();
      }
    }
  }

// move all letters to random position and assign random color, start slowly moving to back from there
//
void randomMove() {
  uint8_t currentHue[114];
  // move to random start position
  // letters
  for (int row=1; row<=10; ++row) {
    for (int column=1; column<=11; ++column) {
      unsigned int servomin = pgm_read_word_near(SERVOMIN_CAL + servonum(row,column));
      unsigned int servomax = pgm_read_word_near(SERVOMAX_CAL + servonum(row,column));
      unsigned int pos = random(servomin,servomax);
      currentHue[servonum(row,column)] = random(0,255);
      moveServo(row,column,pos);
      lightLED(row,column,currentHue[servonum(row,column)],255,255);
      FastLED.show();
      delay(100);
    }
  }
    // dots
    for (int column=1; column<=4; ++column) {
      unsigned int servomin = pgm_read_word_near(SERVOMIN_CAL + servonum(11,column));
      unsigned int servomax = pgm_read_word_near(SERVOMAX_CAL + servonum(11,column));
      unsigned int pos = random(servomin,servomax);
      currentHue[servonum(11,column)] = random(0,255);
      moveServo(11,column,pos);
      lightLED(11,column,currentHue[servonum(11,column)],255,255);
      FastLED.show();
      delay(100);
    }
  
  // simultaneously move all servos backward
  int n = 0;
  while (n<114) {
    n = 0;
    for (int row=1; row<=10; ++row) {
      for (int column=1; column<=11; ++column) {
        unsigned int servomin = pgm_read_word_near(SERVOMIN_CAL + servonum(row,column));
        unsigned int pos = currentPos[servonum(row,column)];
        if(pos>servomin) {
          pos--;
          currentHue[servonum(row,column)]++;
          moveServo(row,column,pos);
          lightLED(row,column,currentHue[servonum(row,column)],255,255);
        } 
        else {
          n++;
        } 
      }
    }
    for (int column=1; column<=4; ++column) {
        unsigned int servomin = pgm_read_word_near(SERVOMIN_CAL + servonum(11,column));
        unsigned int pos = currentPos[servonum(11,column)];
        if(pos>servomin) {
          pos--;
          currentHue[servonum(11,column)]++;
          moveServo(11,column,pos);
          lightLED(11,column,currentHue[servonum(11,column)],255,255);
        } 
        else {
          n++;
        } 
      }
    FastLED.show();
    delay(10);
  }
}

// update dots showing minutes
//
void updateMinutes(int effect) {
  hueDots = random(0,255);
  int ndots = (minute(c_time) % 5);
  for (int i=0; i<ndots; ++i) {
    if (effect>0) {
      unsigned int servomax = pgm_read_word_near(SERVOMAX_CAL + servonum(11,i+1));
      moveServo(11,i+1,servomax);
    }
    lightLED(11,i+1,hueDots,255,255);
    FastLED.show();
    delay(150);
  }
}

// update dot showing seconds
//
void updateSeconds() {
  unsigned long currentMillis = millis();
  unsigned long periodMillis = currentMillis - lastMillis;
  unsigned long milliseconds;
  int ndots = (minute(c_time) % 5);
    unsigned int servomin = pgm_read_word_near(SERVOMIN_CAL + servonum(11,ndots+1));
    unsigned int servomax = pgm_read_word_near(SERVOMAX_CAL + servonum(11,ndots+1));
    if (periodMillis<1000) {
      milliseconds = second(c_time)*1000 + periodMillis;
    }
    else {
      milliseconds = second(c_time)*1000;
    }
    unsigned int pos = map(milliseconds,0,60000,servomin,servomax);
    uint8_t currentHue = map(milliseconds,0,60000,gHue,hueDots);
    moveServo(11,ndots+1,pos);
    lightLED(11,ndots+1,currentHue,255,255);
    lastMillis = currentMillis;
  FastLED.show();
  //delay(10);
}

// reset letters (move all letters to back, light all LEDs with same color)
//
void resetLetters() {
  gHue = random(0,255);
  // letters
  for (int row=1; row<=10; ++row) {
    for (int column=1; column<=11; ++column) {
      lightLED(row,column,gHue,255,255);
      FastLED.show();
      unsigned int servomin = pgm_read_word_near(SERVOMIN_CAL + servonum(row,column));
      if (currentPos[servonum(row,column)]>servomin) {
        moveServo(row,column,servomin);
        delay(50);
      }
    }
  }
  // dots
  for (int column=1; column<5; column++) {
    lightLED(11,column,gHue,255,255);
      FastLED.show();
      unsigned int servomin = pgm_read_word_near(SERVOMIN_CAL + servonum(11,column));
      if (currentPos[servonum(11,column)]>servomin) {
        moveServo(11,column,servomin);
        delay(50);
      }
  } 
}

// initialize matrix (move all servos to back, turn off LEDs)
//
void initMatrix() {
  // letters
  for (int row=1; row<11; row++) {
    for (int column=1; column<12; column++) {
      unsigned int servomin = pgm_read_word_near(SERVOMIN_CAL + servonum(row,column));
      moveServo(row,column,servomin);
      lightLED(row,column,0,0,0);
      FastLED.show();
      delay(100);
    }
  }
  // dots
  for (int column=1; column<5; column++) {
    unsigned int servomin = pgm_read_word_near(SERVOMIN_CAL + servonum(11,column));
    moveServo(11,column,servomin);
    lightLED(11,column,0,0,0);
    FastLED.show();
    delay(100);
  }
}

void switchOnOff() {
  if (gStatus==1) {
    gStatus = 0;
    initMatrix();
  }
  else {
    gStatus = 1;
    updateTime(2);
  }
}

// move servo to position
//
void moveServo(int row, int column, unsigned int pos) {

  // check if position is within limits
  unsigned int servomax = pgm_read_word_near(SERVOMAX_CAL + servonum(row,column));
  unsigned int servomin = pgm_read_word_near(SERVOMIN_CAL + servonum(row,column));
  if (pos<=servomax && pos>=servomin) {

  if(column<4&&row<6) {
     int i = (column-1)*5+(row-1);
     pwm1.setPWM(i,0,pos);
  }
  else if (column>3&&column<7&&row<6) {
    int i = (column-4)*5+(row-1);
    pwm2.setPWM(i,0,pos);
  }
  else if (column>6&&column<10&&row<6) {
    int i = (column-7)*5+(row-1);
    pwm3.setPWM(i,0,pos);
  }
  else if (column>9&&row<6) {
    int i = (column-10)*5+(row-1);
    pwm4.setPWM(i,0,pos);
  }
  else if(column<4&&row>5&&row<11) {
     int i = (column-1)*5+(row-6);
     pwm5.setPWM(i,0,pos);
  }
  else if(column>3&&column<7&&row>5&&row<11) {
     int i = (column-4)*5+(row-6);
     pwm6.setPWM(i,0,pos);
  }
  else if(column>6&&column<10&&row>5&&row<11) {
     int i = (column-7)*5+(row-6);
     pwm7.setPWM(i,0,pos);
  }
  else if(column>9&&row>5&&row<11) {
     int i = (column-10)*5+(row-6);
     pwm8.setPWM(i,0,pos);
  }
  else if(row==11) {
    if(column==1) {
      pwm5.setPWM(15,0,pos);
    }
    else if(column==2) {
      pwm6.setPWM(15,0,pos);
    }
    else if(column==3) {
      pwm7.setPWM(15,0,pos);
    }
    else if(column==4) {
      pwm8.setPWM(15,0,pos);
    }
  }
  currentPos[servonum(row,column)] = pos;
  
  }
}



// light up LED
//
void lightLED(int row, int column, int hue, int saturation, int brightness) {

  int i;
  if(row % 2 == 0) {
      // even rows run backwards
      i = (row-1)*11 + 11-column;
    } else {
      // odd rows run forwards
      i = (row-1)*11 + column-1;
    }
    
  leds[i] = CHSV( hue, saturation, brightness);
    
}

// servo number for specific row and column as specified in the servo position variables 
int servonum(int row, int column){
  return (row-1)*11 + column-1;
}


// readout IR receiver
//
void readIR() {
  if (irrecv.decode(&results)){
    Serial.println(results.value, HEX);
    switch(results.value) {
      case 0xFFA25D:  // "CH-": switch on/off
        switchOnOff();
        break;
      case 0xFF30CF: // "1": update display using typing effect
        updateTime(2);
        break;
      case 0xFF18E7:  // "2": update display using blend in effect
        updateTime(1);
        break;
      case 0xFF7A85:  // "3": random move
        randomMove();
        updateTime(1);
        break;
      case 0xFF22DD:  // "|<<": decrease hour
        setTime(c_time);
        adjustTime(-3600);
        c_time = now();
        RTC.set(c_time);
        updateTime(0);
        lastmin = minute(c_time);
        break;
      case 0xFF02FD:  // ">>|": increase hour
        setTime(c_time);
        adjustTime(3600);
        c_time = now();
        RTC.set(c_time);
        updateTime(0);
        lastmin = minute(c_time);
        break;
      case 0xFFE01F:  // "-": decrease minutes
        setTime(c_time);
        adjustTime(-60);
        c_time = now();
        RTC.set(c_time);
        updateTime(0);
        lastmin = minute(c_time);
        break;
      case 0xFFA857:  // "+": increase minutes
        setTime(c_time);
        adjustTime(60);
        c_time = now();
        RTC.set(c_time);
        updateTime(0);
        lastmin = minute(c_time);
        break;
    }
    irrecv.resume();
  }
}

Custom parts and enclosures

CAD drawing - frame 01
CAD drawing - frame 02
STL files - letter boxes
word_matrix_v5_n9fGBtbXYE.zip
STL files - linear actuator

No document.

Schematics

main schematics
Servo word clock steckplatine cmbhxzhgqw

Comments

Similar projects you might like

Arduino OLED Word Clock

Project showcase by garysat

  • 4,648 views
  • 1 comment
  • 13 respects

Arduino to-the-minute word clock OLED Display

Project showcase by garysat

  • 1,700 views
  • 0 comments
  • 3 respects

Minimal Parts OLED Word Clock

Project showcase by

  • 1,274 views
  • 0 comments
  • 5 respects

Italian Word Clock

Project tutorial by Tittiamo

  • 5,642 views
  • 5 comments
  • 15 respects

Arduino Word Clock on 8x8 RGB LED Matrix

Project tutorial by Mirko Pavleski

  • 1,797 views
  • 4 comments
  • 12 respects

Android Things Word Clock

Project tutorial by Daniele Bonaldo

  • 20,064 views
  • 7 comments
  • 116 respects
Add projectSign up / Login