BobS
Published © GPL3+

Animated Word Clock on the O-Watch

A modified version of J Koger's Mini Word Clock for O-Watch, now with an animated digital rain effect.

BeginnerShowcase (no instructions)1 hour1,527
Animated Word Clock on the O-Watch

Things used in this project

Hardware components

O Watch Base Kit
O Watch Base Kit
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Code

animated_wordclock.ino

Arduino
/*
 * Animated Word Watch
 *
 * Demonstrates the use of the Arduino Zero Real-Time-Clock (RTC) library to make a simple 8 x 8 'Word Clock'
 * display of the current hours and minutes.
 * 
 * This type of display only shows the current minutes (to within 5 minutes) and current hour, using a grid of 8 x 8 letters. The letters
 * needed to display the current time (for example, 'HALF PAST TEN') are 'lit up' (drawn with a bright color like yellow), and the
 * unnneeded letters are 'turned off' (drawn with a dull color like gray).
 * 
 * Since the TinyScreen is 64 pixels high, the 8 x 8 grid of letters will take up most of the display, since the
 * smallest font provided on the O-Watch is 7 pixels tall (plus 1 pixel gap between rows): (7 + 1) * 8 = 64 pixels needed 
 * to display 8 rows. That doesn't leave any room for the date, day of the week, or seconds. It's also a bit hard to read
 * since the letters are pretty, well, tiny.
 *
 * Uses Arduino Zero RTC library https://www.arduino.cc/en/Reference/RTC
 * Maintained by Arturo Guadalupi https://github.com/arduino-libraries/RTCZero
 *
 * This code is for the TinyScreen+ board by TinyCircuits used by O Watch http://tiny-circuits.com 
 *
 * This example is based on the Simple Watch code created by O Watch on 6 March 2016 http://theowatch.com
 * Modified to use RTC, to set the display brightness based on time of day, and to use __DATE__ and __TIME__
 * to preset the watch's date and time by J Koger 13 March 2016
 * 
 * Modified to display time as an 8 x 8 character 'Micro Word Clock' by J Koger 16 March 2016, using a tweaked
 * version of the code from Daniel Rojas' https://github.com/formatc1702/Micro-Word-Clock
 * 
 * Modified to display an animated digital rain effect by Bob S on 19 March 2016
 * 
*/

#include <TinyScreen.h> //include TinyScreen library
#include <RTCZero.h> //include the Arduino Zero's Real Time Clock library
#include <stdio.h>  // include the C++ standard IO library

/* Create an rtc object */
RTCZero rtc;

// We'll dynamically change these values to set the current initial time
// No need to change them here.
byte seconds = 0;
byte minutes = 45;
byte hours = 9;

// We'll dynamically change these values to set the current initial date
// The preset values are only examples.
byte days = 13;
byte months = 3;
byte years = 16;

int brightness = 15; // We'll set it, 3 - 15 based on time of day

TinyScreen display = TinyScreen(TinyScreenPlus); //Create the TinyScreen object

// Here we define several colors for the gradient on the droplet trail, from bright to dark
#define DROPLETTRAILLENGTH 9
const uint16_t droplettrail[DROPLETTRAILLENGTH] = { 0x9FF6, 0x07E7, 0x0606, 0x0545, 0x0464, 0x0323, 0x0282, 0x0141, 0x0080 };

// Our colors for letters that are off and
// for letters that are lit.
#define LETTER_OFF  droplettrail[DROPLETTRAILLENGTH-2]
#define LETTER_LIT  droplettrail[1]

// This is the 8 x 8 grid of letters used to display the current time.
// We'll only 'light up' the letters needed to show the current
// time, and draw all the other ones in a 'dark' color.

char wordClock[8][9] = { 
 "HATWENTY",
 "FIFVTEEN",
 "LF*PASTO",
 "NINEIGHT",
 "ONETHREE",
 "TWELEVEN",
 "FOURFIVE",
 "SIXSEVEN"
};

// This is an array used to determine which letters should be 'lit'
// to display the current minutes (or, really, the current 5-minute
// segment. There is one grid for each segment, and since there are 12
// 5-minute segments in an hour, there are 12 grids in the array.
//
// For example, if the current time is 10:06, then it is roughly
// 5 minutes past 10. So, we'll want the letters F I V E and P A S T
// to be lit. Look in the second grid for 'five past'; it has 1's for
// each of the wanted letters and 0's for all of the unused letters.
//
// The lit/unlit letter indicators are stored as bits within bytes
// (a byte holds 8 bits). We could use a byte for each letter, but
// bits only need 1/8 the space. And it'll give us an excuse to have
// some fun with bit comparisons later on!

unsigned char letterMinutes[12][8] ={
  { // o'clock
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  , { // five past
    0B00000000,
    0B11010100,
    0B00011110,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  , { // ten past
    0B00000000,
    0B00001101,
    0B00011110,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // fifteen past
    0B00000000,
    0B11101111,
    0B00011110,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // twenty past
    0B00111111,
    0B00000000,
    0B00011110,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // twentyfive past
    0B00111111,
    0B11010100,
    0B00011110,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // half past
    0B11000000,
    0B00000000,
    0B11011110,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // twentyfive to
    0B00111111,
    0B11010100,
    0B00000011,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // twenty to
    0B00111111,
    0B00000000,
    0B00000011,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // fifteen to
    0B00000000,
    0B11101111,
    0B00000011,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // ten to
    0B00000000,
    0B00001101,
    0B00000011,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // five to
    0B00000000,
    0B11010100,
    0B00000011,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
};

// These are the corresponding
// grids to determine which letters
// should be 'lit' to show the
// current hour, 1 grid for each of
// the 12 hours.

unsigned char letterHours[12][8] ={
  { // twelve
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B11110110,
    0B00000000,
    0B00000000
  }
  ,{ // one
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B11100000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // two
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B11000000,
    0B01000000,
    0B00000000
  }
  ,{ // three
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00011111,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // four
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B11110000,
    0B00000000
  }
  ,{ // five
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00001111,
    0B00000000
  }
  ,{ // six
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B11100000
  }
  ,{ // seven
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00011111
  }
  ,{ // eight
    0B00000000,
    0B00000000,
    0B00000000,
    0B00011111,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // nine
    0B00000000,
    0B00000000,
    0B00000000,
    0B11110000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000
  }
  ,{ // ten
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000001,
    0B00000001,
    0B00000001,
    0B00000000,
    0B00000000
  }
  ,{ // eleven
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00000000,
    0B00111111,
    0B00000000,
    0B00000000
  }
};



void setup()
{
  char s_month[5];
  int tmonth, tday, tyear, thour, tminute, tsecond;
  static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";

  display.begin();                            //Initializes TinyScreen board
  display.setFlip(1);                         //Flips the TinyScreen rightside up for O Watch
  display.setBitDepth(1);                     // allow 16bit colors BGR565
  display.on();                               //Turns TinyScreen display on
  display.fontColor(LETTER_OFF, TS_16b_Black); //Set the font color, font background
  display.setFont(thinPixel7_10ptFontInfo);   //Set the font type


  rtc.begin(); // initialize RTC
  
  // Set the time and date. Change this to your current date and time.
  // setTime(16,19,00,12,3,2016);    //values in the order hr,min,sec,day,month,year

  // Let's be lazy and let the compiler set the current date and time for us.
  // This will be a few seconds off due to the time it takes to compile the
  // .ino file and upload the app. But pretty close.

  // __DATE__ is a C++ preprocessor string with the current date in it.
  // It will look something like 'Mar  13  2016'.
  // So we need to pull those values out and convert the month string to a number.
  sscanf(__DATE__, "%s %d %d", s_month, &tday, &tyear);

  // Similarly, __TIME__ will look something like '09:34:17' so get those numbers.
  sscanf(__TIME__, "%d:%d:%d", &thour, &tminute, &tsecond);

  // Find the position of this month's string inside month_names, do a little
  // pointer subtraction arithmetic to get the offset, and divide the
  // result by 3 since the month names are 3 chars long.
  tmonth = (strstr(month_names, s_month) - month_names) / 3;

  months = tmonth + 1;  // The RTC library expects months to be 1 - 12.
  days = tday;
  years = tyear - 2000; // The RTC library expects years to be from 2000.
  hours = thour;
  minutes = tminute;
  seconds = tsecond;

  rtc.setTime(hours, minutes, seconds);
  rtc.setDate(days, months, years);
  
}

// animated digital rain affect
void displayTime() {

  hours = rtc.getHours();
  minutes = rtc.getMinutes();
  seconds = rtc.getSeconds();
  
  // set brightness
  if (hours <= 12)
    brightness = hours + 3; // 0 hours = 3 brightness, noon = 15
  else if (hours >= 18)
    brightness = (24 - hours) * 2 + 2;  // 23 hours = 4 brightness, 18 hours = 14
  else
    brightness = 15; // full brightness all afternoon
  if (brightness < 3)
    brightness = 3;
  if (brightness > 15)
    brightness = 15;
  display.setBrightness(brightness);       

  display.clearScreen(); 
  display.on();   
  
  // Convert 24-hour time into the 12-hour format for
  // the word clock.
  int wordHours, wordMinutes;
  wordHours = hours;
  if (minutes >= 35)   // After half-past the hour, the minute words start referencing the next hour.
    wordHours++;
  wordHours %= 12;    // Now 0 - 11, which is what we need for lookups in the grid.

  // Convert minutes to 5-minute segments
  wordMinutes = minutes / 5;

  // Get the letter-selection grids for the current hour and 5-minute segment.
  unsigned char *thisLetterMinute = letterMinutes[wordMinutes];
  unsigned char *thisLetterHour = letterHours[wordHours];
                                
  // the droplet in each column starts at a different position, mostly offscreen
  randomSeed(minutes*seconds);
  signed char droplet[8];
  for (int column = 0; column < 8; column++) {
    droplet[column] = - random(2*DROPLETTRAILLENGTH);
  }

  // keep track of how many times a droplet goes offscreen
  unsigned int dropletOffscreen[8] = {0,0,0,0,0,0,0,0};
  // total times droplets go offscreen
  const int dropletRuns = 3;

  bool notdone = true;
  while( notdone ) {
    // move droplets down the rows of the columns
    for (int row = 0; row < 8; row++) 
    {
      unsigned char letterColumn = B10000000;
      for (int column = 0; column < 8; column++, letterColumn = letterColumn >> 1)
      {
        
        // Set the drawing cursor to the position of the current letter.
        display.setCursor((column * 10) + 10, (row * 8));

        if (row <= (droplet[column] - DROPLETTRAILLENGTH)) {     
          // leave a ghost of the unlit letters for the final run, when highlighting the time
          if (dropletOffscreen[column] >= dropletRuns) {
            // Check to see if the current letter should be off or lit.
            if (((thisLetterMinute[row] & letterColumn) == 0) &&
                ((thisLetterHour[row] & letterColumn) == 0)) {
              // ghost of non-used letters
              display.fontColor(LETTER_OFF, TS_16b_Black);
            } else {
              //  highlight the letters needed to display the time
              display.fontColor(LETTER_LIT, TS_16b_Black);
            }
  
          } else {
            // hidden, after the trail
            display.fontColor(TS_16b_Black, TS_16b_Black); 
          } 
        } else if (row <= droplet[column] && row > (droplet[column] - DROPLETTRAILLENGTH)) {
          // set the color for the droplet or its trail
          display.fontColor(droplettrail[droplet[column] - row ], TS_16b_Black);
        } else {
            // hidden, before the head
            display.fontColor(TS_16b_Black, TS_16b_Black); 
        }

        // randomize until the last frames, then display the correct word clock face
        if (dropletOffscreen[column] >= dropletRuns && row < droplet[column]) {
          display.print(wordClock[row][column]);
        } else {
          display.print(wordClock[random(8)][random(8)]);
        }
      }
    }
    
    // move the droplets to the next row, count any that moved offscreen
    for (int column = 0; column < 8; column++) {
      droplet[column] ++;
      
      // if droplet has gone offscreen, and it hasn't completed it's runs, then increment its counter
      if (droplet[column] > (7 + DROPLETTRAILLENGTH)) {
        
        if (dropletOffscreen[column] < dropletRuns) {
          // we have more runs to complete, reset the droplet to the top
          droplet[column] = - random(DROPLETTRAILLENGTH);
        }
        
        // count it
        dropletOffscreen[column] ++;
      } 
      
      // check bounds
      if (dropletOffscreen[column] > dropletRuns){
        // hold the end position
        droplet[column] = (7 + DROPLETTRAILLENGTH);
      }
    }
    
    // if all droplets are done, break from the while loop
    notdone = false;
    for (int column = 0; column < 8; column++) {
      if ( dropletOffscreen[column] <= dropletRuns ){
        // not done yet
        notdone = true;
      }
    }
    
    // delay between frames
    delay(45);
  }

  // hold display of the time for a few seconds
  delay(5000);
  display.off();
}


void loop()
{
   int button = display.getButtons();
   if (button) {
      displayTime();
   }
   delay(10);
}

Credits

BobS

BobS

2 projects • 1 follower
Thanks to J Koger.

Comments