Hardware components | ||||||
| × | 1 |
Now that our O-Watch can display a round analog clock face, it's time to get silly!
Inspired by Mike Mak's Eyeclock, the O-Watch can show the time with a pair of googly eyes! The left eye acts like the hour hand on a clock, and the right eye acts like a minute hand.
(In the example picture, the time is 10:14. The light-blue stripes in the picture are caused by interference between the O-Watch's display and my camera. They aren't part of the Googly Eyes.)
It is strongly recommended that you get the previous Simple RTC Watch working before trying this.
Note: This watch face is now included in Mult-O-Watch.
This time, we'll tweak the drawCircle() routine to fill in a circle by drawing lines between the circle's outline pixels. We'll also tweak the routine for drawing the hour and minute hands to just figure out where the endpoint of the hands are, which we'll use as the centerpoint for the eyes' pupils.
/*
* Google Eye Watch
*
* Demonstrates the use of the... awwww, it's just a silly watch. Two big eyes
* show the time: the position of the left eye's pupil shows the hour, and the right
* eye's pupil shows the minute.
*
* Inspired by Mike Mak's Eye Clock: http://www.mikemak.com/mike/main_2.php?id=11
*
* 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
* Modified to just display the hours and minutes as two googly eyes by J Koger 16 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
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
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;
}
}
}
// We could draw a solid circle by drawing an outer circle,
// then one that's one pixel shorter radius inside of it,
// and another inside of it, etc.
//
// But instead we'll just draw lines between all the outline pixels
// we'd otherwise draw.
void drawSolidCircle(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);
display.drawLine(x + x0, -y + y0 , x + x0, y + y0, color);
display.drawLine(y + x0, -x + y0 , y + x0, x + y0, color);
display.drawLine(-x + x0, -y + y0 , -x + x0, y + y0, color);
display.drawLine(-y + x0, -x + y0 , -y + x0, x + y0, color);
y++;
if (radiusError<0)
{
radiusError += 2 * y + 1;
}
else
{
x--;
radiusError += 2 * (y - x) + 1;
}
}
}
// Find the endpoint of 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 findRadiusEndpoint(int xStart, int yStart, int angle, int radius, int *xEnd, int *yEnd)
// 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
// xEnd and yEnd are pointers to ints that will be filled in with the endpoint
{
// 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 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));
}
void displayTime()
{
display.on(); //Turns TinyScreen display on
enum
{
leftEyeCenterX = 23,
leftEyeCenterY = 32,
rightEyeCenterX = 73, // 96 - 23
rightEyeCenterY = 32,
eyeRadius = 22,
pupilRadius = 16,
backgroundColor = TS_8b_Blue,
eyeColor = TS_8b_White,
pupilColor = TS_8b_Black,
pupilRadialCenter = eyeRadius - pupilRadius
};
display.drawRect(0, 0, 96, 64, 1, backgroundColor); // Give our googly eyes a nice blue background. Cooookeeee!!!
for (int i = 0; i < 2; i++) // Display for 2*5000 milliseconds (10 seconds), update/blink eyes each period
{
// The second time around, this full redraw will make the eyes look like they're blinking!
drawSolidCircle(leftEyeCenterX, leftEyeCenterY, eyeRadius, eyeColor);
drawSolidCircle(rightEyeCenterX, rightEyeCenterY, eyeRadius, eyeColor);
drawCircle(leftEyeCenterX, leftEyeCenterY, eyeRadius, pupilColor);
drawCircle(rightEyeCenterX, rightEyeCenterY, eyeRadius, pupilColor);
// Get the hours and minutes, one for each eye.
hours = rtc.getHours();
minutes = rtc.getMinutes();
// 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;
int leftPupilCenterX, leftPupilCenterY, rightPupilCenterX, rightPupilCenterY;
findRadiusEndpoint(leftEyeCenterX, leftEyeCenterY, hourAngle, pupilRadialCenter, &leftPupilCenterX, &leftPupilCenterY);
findRadiusEndpoint(rightEyeCenterX, rightEyeCenterY, minutes, pupilRadialCenter, &rightPupilCenterX, &rightPupilCenterY);
drawSolidCircle(leftPupilCenterX, leftPupilCenterY, pupilRadius, pupilColor);
drawSolidCircle(rightPupilCenterX, rightPupilCenterY, pupilRadius, pupilColor);
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
delay(5000); //display for 5 seconds
}
display.off(); //Turns TinyScreen display off
}
void loop()
{
if (display.getButtons())
{
displayTime();
}
}
Comments