J Koger
Published © GPL3+

Simple Analog Watch using RTC

We take the Simple RTC Watch one step further by using the TinyScreen graphics functions to draw a round analog watch face!

BeginnerProtip1 hour1,584
Simple Analog Watch using RTC

Things used in this project

Hardware components

O Watch Base Kit
O Watch Base Kit
×1

Story

Read more

Code

simple_rtc_analog_watch.ino

Arduino
Displays the current time, date, day, AND a round analog clock on the O-Watch
/*
 * Simple RTC Analog Watch
 *
 * Demonstrates the use of the Arduino Time library and Arduino Zero Real-Time-Clock (RTC) library to make a simple digital
 * and simulated analog clock. The digits are displayed to the left, the round analog clock face to the right.
 *
 * Uses Arduino Time library http://playground.arduino.cc/code/time
 * Maintained by Paul Stoffregen https://github.com/PaulStoffregen/Time
 *
 * 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 also display a round analog clock by J Koger 14 March 2016
 * 
*/

#include <TinyScreen.h> //include TinyScreen library
#include <TimeLib.h> //include the Arduino Time 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

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.on();                               //Turns TinyScreen display on
  display.fontColor(TS_8b_White,TS_8b_Black); //Set the font color, font background

  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);
}

// Draw a circle. Slow but works. Taken from the O-Watch TinyScreen tutorial.
// http://www.theowatch.com/learn/o-watch-tinyscreen-programming/learn-tinyscreen-demo/
// 
void drawCircle(int x0, int y0, int radius, uint8_t color)
{
  int x = radius;
  int y = 0;
  int radiusError = 1-x;
 
  while(x >= y)
  {
    //drawPixel(x,y,color);//set pixel (x,y) to specified color. This is slow because we need to send commands setting the x and y, then send the pixel data.
    display.drawPixel(x + x0, y + y0, color);
    display.drawPixel(y + x0, x + y0, color);
    display.drawPixel(-x + x0, y + y0, color);
    display.drawPixel(-y + x0, x + y0, color);
    display.drawPixel(-x + x0, -y + y0, color);
    display.drawPixel(-y + x0, -x + y0, color);
    display.drawPixel(x + x0, -y + y0, color);
    display.drawPixel(y + x0, -x + y0, color);
    y++;
    if (radiusError<0)
    {
      radiusError += 2 * y + 1;
    }
    else
    {
      x--;
      radiusError += 2 * (y - x) + 1;
    }
  }
}


// Draw a line from a given start point at a given angle for a given length.
//
// Don't use any floating point math such as the sine() function because it
// takes a long time to run and eats battery life on a computer without
// a floating point processor, like the Arduino Zero in the O-Watch.
// Instead, use a good old-fashioned hard-coded sine lookup table and
// a couple of integer math tricks.
//
// The underlying idea from a geometry standpoint is that we need to figure
// out the equivalent X,Y endpoint of a line that goes from the center of the
// circle (0,0) out to a length of radius 1.0 (the 'unit circle') at a given
// angle and multiply the length of our hand by that X,Y endpoint, then slide
// both the center of the circle and the enpoint over so that they match our actual.
// circle's coordinates.
// The sine and cosine of the unit circle are the X,Y endpoint that we need.
//
// Modified from: https://codebender.cc/example/glcd/clockFace#AnalogClock.cpp

void drawHand(int xStart, int yStart, int angle, int radius, unsigned char color)

// xStart, yStart are starting point of hand (center of clock face)
// angle is location of hand on dial (0-60)
// radius is length of hand
{
  // Sines for hand positions from 0 to 15 minutes. All of the other
  // hand position sines can be determined by flipping x and y. These
  // are all multiplied by 256 so that we can use integers to store them.
  // Cosines can be also be determined by taking the sine of the angle minus
  // 15 degrees and flipping x and y.
  static int sine[16] = {0, 27, 54, 79, 104, 128, 150, 171, 190, 201, 221, 233, 243, 250, 254, 255};
  int xEnd, yEnd, quadrant, x_flip, y_flip;

  // calculate which quadrant the hand lies in
  quadrant = angle/15 ;

  // We could have a bigger table of sine values that handle every hand position from 0 to 60,
  // but we can avoid using that memory by doing some quick reflections and rotations.
  switch ( quadrant )
  {
    case 0 : x_flip = 1 ; y_flip = -1 ; break ;
    case 1 : angle = abs(angle-30) ; x_flip = y_flip = 1 ; break ;
    case 2 : angle = angle-30 ; x_flip = -1 ; y_flip = 1 ; break ;
    case 3 : angle = abs(angle-60) ; x_flip = y_flip = -1 ; break ; 
    default:  x_flip = y_flip = 1; // this should not happen
  }

  xEnd = xStart;
  yEnd = yStart;
  // OK, here's the tricky part. Look up the sine for the hand
  // angle, then multiply it by the hand length. Then, because
  // our sine table values are all pre-multiplied by 256 so we
  // can store them as integers, we need to divide by 256 to
  // correct for that. Which is, in integer math, the same as
  // saying that we'll shift the values right by 8 bit positions.
  // Doing a shift-right is faster than doing an integer divide by 256
  // (the compiler is probably smart enough to turn that
  // divide by 256 into a right shift anyway, but let's do
  // an explicit shift because it's cool).
  //
  // The equivalent floating-point call would look something like:
  // xEnd = sine(angle) * radius;
  xEnd += (x_flip * (( sine[angle] * radius ) >> 8));
  yEnd += (y_flip * (( sine[15-angle] * radius ) >> 8));

  display.drawLine(xStart, yStart, xEnd, yEnd, color);
}


void displayTime()
{

  display.on();                               //Turns TinyScreen display on

  // Draw the circle for the analog clock. According to the TinyScreen
  // tutorial http://www.theowatch.com/learn/o-watch-tinyscreen-programming/
  // the TinyScreen is 96 pixels wide by 64 high, with (0,0) in the
  // upper-left corner, and (95,63) in the lower right.
  // This is the only part of the time display that doesn't need to be redrawn
  // as long as the hands stay inside the circle.
  //
  // We want a nice big clock face, so the circle should be 64 pixels tall,
  // and thus 64 pixels wide, with a radius of 32 pixels. Well, that ends up drawing
  // the edges of the circle slightly off the screen, so we'll make the circle
  // a little smaller, let's say 60 pixels in diameter, or 30 radius. To give a little
  // room for text to the left, we'll put the circle all the way to the right
  // of the TinyScreen, so put its center at 95 - 30 = 65. The center needs
  // to be halfway down the screen, or 63 / 2 = 32.
  //
  // Since this is a sporty watch, we'll give it a red circle.
  //
  // Let's figure out the hands while we're at it. The minute hand should
  // just fit within the circle, maybe a little shorter than that, so let's
  // give it a length (radius) of 28 pixels. The hour hand should be much
  // shorter than the minute hand, so how about 18 pixels. The second hand
  // can be the same size as the minute hand, but a different color, how
  // about a nice sporty yellow.

 enum
 {
    clockCenterX = 65,
    clockCenterY = 32,
    clockCircleRadius = 30,
    clockCircleColor = TS_8b_Red,
    clockMinuteHandLength = 28,
    clockMinuteHandColor = TS_8b_White,
    clockHourHandLength = 18,
    clockHourHandColor = TS_8b_White,
    clockSecondHandLength = 28,
    clockSecondHandColor = TS_8b_Yellow
  };

  drawCircle(clockCenterX, clockCenterY, clockCircleRadius, clockCircleColor);
  
  for (int i = 1; i < 15; i++) // Display for 15*1000 milliseconds (15 seconds), update display each second
  {
    // To correctly handle switching date at midnight while the time display is on,
    // update -everything-, not just the seconds.

    display.setFont(liberationSansNarrow_12ptFontInfo);   //Set the font type

    // Get the complete date and time now since they're used for the weekday
    // and brightness calculations.
    months = rtc.getMonth();
    days = rtc.getDay();
    years = rtc.getYear();
    hours = rtc.getHours();
    minutes = rtc.getMinutes();
    seconds = rtc.getSeconds();

    // First draw the clock hands, to minimize them flickering.

    // The hour is 0 - 23. But we need it
    // to be an 'angle' of 0 - 59. So
    // convert 24-hour time to 12 hour time,
    // and then multiply by 60 / 12 = 5.
    int hourAngle;
    if (hours >= 12)
      hourAngle = hours - 12;
    else
      hourAngle = hours;
    hourAngle *= 5;
    // To give the hour hand a real
    // analog-clock feel, it shouldn't point
    // straight at the hour, but should move between
    // the hours as the minute hand sweeps around.
    // So, let's nudge it forward a smidge by
    // the minutes scaled down to the space within 1 hour, or
    // divide them by 12.
    hourAngle += minutes / 12;
    
    
    drawHand(clockCenterX, clockCenterY, hourAngle, clockHourHandLength, clockHourHandColor);
    drawHand(clockCenterX, clockCenterY, minutes, clockMinuteHandLength, clockMinuteHandColor);
    drawHand(clockCenterX, clockCenterY, seconds, clockSecondHandLength, clockSecondHandColor);
    

    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);                  //Set display brightness 0 - 15


   // Print date in US format MM:DD:YY (Switch the order in which day, month, year that you like to use)
    display.setCursor(0,8); //Set the cursor where you want to start printing the date
    if(months < 10) display.print(0); //print a leading 0 if hour value is less than 0
    display.print(months);
    display.print("/");
    days = rtc.getDay();
    if (days < 10) display.print(0);
    display.print(days); 
    // display.print("/");
    // display.print(years);

    display.setCursor(0,25); //Set the cursor where you want to start printing the date  
    setTime(hours,minutes,seconds,days,months,2000 +  years);    //values in the order hr,min,sec,day,month,year
    char wkday[16];
    strcpy(wkday, dayStr(weekday()));
    wkday[3] = ' '; wkday[4] = '\0';
    display.print(wkday); // To keep the text compact, only print the first 3 letters.

    display.setFont(liberationSansNarrow_12ptFontInfo);   //Set the font type

    // display time in HH:MM:SS 24 hour format
    display.setCursor(0,45); //Set the cursor where you want to start printing the time
    if(hours < 10) display.print(0); //print a leading 0 if hour value is less than 0
    display.print(hours);
    display.print(":");
    if(minutes < 10) display.print(0); //print a leading 0 if minute value is less than 0
    display.print(minutes);
    // display.print(":");
    // if(seconds < 10) display.print(0); //print a leading 0 if seconds value is less than 0
    // display.print(seconds);
    // display.print(" "); //just a empty space after the seconds

   
    delay(1000); //display for 1 seconds
    
    // Now erase the clock hands by drawing them in black.
    drawHand(clockCenterX, clockCenterY, hourAngle, clockHourHandLength, TS_8b_Black);
    drawHand(clockCenterX, clockCenterY, minutes, clockMinuteHandLength, TS_8b_Black);
    drawHand(clockCenterX, clockCenterY, seconds, clockSecondHandLength, TS_8b_Black);
  }

  display.off();                               //Turns TinyScreen display off
}

void loop()
{
   if (display.getButtons())
   {
      displayTime();
   }
}

Credits

J Koger

J Koger

10 projects • 4 followers

Comments