Hardware components | ||||||
| × | 1 |
Time to show off the O-Watch's colorful TinyScreen, which can display 16-bit colors, and lets you specify the colors in terms of Red, Green, and Blue values.
Note: This watch face is now included in Mult-O-Watch.
Inspired by Jack Hughes' Colour Clock, we'll use the time of day to determine the background color of the watch face. We'll let the current hour (0 to 23) determine the Red level, the minutes (0 - 59) set the Green level, and the seconds (0 - 59) set the Blue level. Then combine them together in the TinyScreen's BBBBBGGGGGGRRRRR 16-bit color and use that for the screen's background color.
We'll also switch the text color from white to black depending on how bright the background color is.
Note: Because the TinyScreen only supports 5 bits for the Blue level, the allowed Blue values are 0 - 31. Since we're using seconds from 0 - 59, we need to adjust them to fit within the allowed values. We could simply scale them down to fit, but it's more fun (and makes the screen more active) to use modulo 32 to only keep the smaller (and constantly changing) part of the seconds value.
Pressing the upper-right button cycles which parts of the time determine which color values. To start with, we'll use Hours->Red, Minutes->Green, and Seconds->Blue, or HMS->RGB. Press the upper-right button to change to HMS->BRG, again to switch to HMS->GBR, and again to switch back to HMS->RGB.
To make the color transitions as smooth as possible, we'll do all of our text drawing to a screen buffer in memory, then copy the entire buffer all at once to the TinyScreen. This is the same way that the Flappy Birdz demo for the O-Watch does it.
We'll also center the text on the screen by using the getPrintWidth() routine to figure out how wide our text is.
/*
* Color Watch
*
* Watch face that changes color based on the RGB values of the current time. It uses 16-bit
* color depth to give us 5 bits of blue and red, and 6 bits of green BBBBBGGGGGGRRRRR.
* The current time is displayed in white if the background is darker, or black if the
* background is lighter. To make the transitions smooth, all text is drawn into a screen
* buffer then sent all at once to the TinyScreen via display.writeBuffer(), similar to
* how the Flappy Birdz demo for the O-Watch does it.
*
* The color is based on red from hours (0 - 23), green from minutes (0 - 59), and blue
* from seconds (0 - 59 modulo 32). Press the upper-right button to rotate the order from
* RGB (H->R, M->G, S->B) to BRG (H->B, M->R, S->G), and again to GBR (H->G, M->B, S->R), and
* again to go back to RGB.
*
* Demonstrates the use of the Arduino Time library and Arduino Zero Real-Time-Clock
* (RTC) library. Very bright and colorful. No dimmer logic on this one!
*
* Inspired by Jack Hughes' Colour Clock http://lookatjack.com/
*
* 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 display the date and time using a vector font by J Koger 20 March 2016
* Modified to make a time-based color watch by J Koger 29 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
// The dimensions of our lovely TinyScreen.
#define SCREEN_WIDTH 96
#define SCREEN_HEIGHT 64
// Determine which color goes with hours, minutes, seconds
#define RGB 1 // Default. Red = hours, green = minutes, blue = seconds
#define BRG 2 // Blue = hours, red = minutes, green = seconds
#define GBR 3 // Green = hours, blue = minutes, red = seconds
/* 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;
byte colorTimeMap = RGB;
TinyScreen display = TinyScreen(TinyScreenPlus); //Create the TinyScreen object
typedef struct
{
uint8_t col[SCREEN_WIDTH * 2];
} ScreenRow;
typedef struct
{
ScreenRow row[SCREEN_HEIGHT];
} ScreenBuffer;
ScreenBuffer scrBuf;
/* Take the contents of our ScreenBuffer and send them
* to the O-Watch's TinyScreen. We'd use DMA transfer
* to speed this up, but that's a little experimental
* right now.
*/
void sendBufferToScreen()
{
display.startData();
for (int y = 0; y < SCREEN_HEIGHT; y++)
{
display.writeBuffer(&(scrBuf.row[y].col[0]), SCREEN_WIDTH * 2);
}
display.endTransfer();
}
/*
* Erase the screen buffer with specified color.
*/
void clearScreenBuffer(uint16_t color)
{
for (int y = 0; y < SCREEN_HEIGHT; y++)
{
for (int x = 0; x < SCREEN_WIDTH; x++)
{
scrBuf.row[y].col[2 * x] = color >> 8;
scrBuf.row[y].col[(2 * x) + 1] = color & 0xFF;
}
}
}
/*
* Draw a single pixel into the screen buffer. Make
* sure it has valid coordinates before drawing it.
*/
void drawPixelInBuffer(int x, int y, uint16_t color)
{
if ((x >= 0) && (x < SCREEN_WIDTH) && (y >= 0) && (y < SCREEN_HEIGHT))
{
scrBuf.row[y].col[2 * x] = color >> 8;
scrBuf.row[y].col[(2 * x) + 1] = color & 0xFF;
}
}
/*
* Print a string of ASCII characters into the screen buffer. This routine uses the
* same font info that the TinyScreen routines do, so as to not use up more RAM with
* a second copy of the font tables.
*/
void printStringInBuffer(int x, int y, char * s, const FONT_INFO& fontInfo, uint16_t textColor, uint16_t backgroundColor)
{
// The character descriptor includes the character's width in pixels,
// and offset into the fontInfo.bitmap table. fontInfo.charDesc
// is a table of one descriptor for each printable ASCII character in the font.
const FONT_CHAR_INFO* fontDescriptor = fontInfo.charDesc;
// The font height is the height of the font in pixels.
int fontHeight = fontInfo.height;
// The character bitmap is one or more bytes per row of
// pixel on/off bits. The number of bytes per row is
// set by fontInfo.charDesc.width / 8 bits per pixel.
// The offset of a character in the bitmap table is set
// by fontInfo.charDesc.offset.
const unsigned char* fontBitmap = fontInfo.bitmap;
// The first ASCII character supported by the font. This
// is typically the space ' ' character.
int fontFirstCh = fontInfo.startCh;
// The last ASCII character supported by the font this is
// typically the tilde '~' character.
int fontLastCh = fontInfo.endCh;
// Walk through the string, and print each character in it.
for (int curChar = 0; s[curChar] != '\0'; curChar++) // Stop at the string's null terminator.
{
// Determine the offset into tables for this character by
// subtracting the ASCII value of the font's first supported character
// from the ASCII value of this character.
int chOffset = s[curChar] - fontFirstCh;
// Start by getting the width in pixels of the current character.
// This is stored in the same table used by the TinyScreen libraries,
// so go get it by doing a near-memory access using the pgm_read_byte()
// macro.
uint8_t chWidth = pgm_read_byte(&fontDescriptor[chOffset].width);
// Figure out how many bytes per row based on the width of the character
// in pixels divided by 8 pixels (bits) per byte. If the number of pixels
// per row doesn't divide neatly by 8, add another byte for the leftovers.
// For example, a character that is 1 pixel wide (like '!' in a thin font)
// still gets 1 byte per row.
int bytesPerRow = chWidth / 8;
if ((chWidth % 8) != 0)
bytesPerRow++;
// Figure out the offset into the bitmap table for this character.
// The character bitmaps are upside down in the font tables from how
// we look at the display, which is to say that the lowest row is first,
// and the top row is last. So, we keep track of the offset into the bitmap
// by starting at the last row of bytes in the bitmap and working backwards.
uint16_t offset = pgm_read_word(&fontDescriptor[chOffset].offset) + (bytesPerRow * fontHeight) - 1;
// Walk through the rows in the current character's bitmap, and look at each
// bit in the current byte to determine if the equivalent pixel is on (draw the
// pixel in textColor) or off (draw the pixel in backgroundColor).
for (uint8_t curRow = 0; curRow < fontHeight; curRow++)
{
int totalBits = 0;
for (int curByte = 0; curByte < bytesPerRow; curByte++)
{
// Get the one-byte bitmap for the current byte within the current row.
// Because the bitmaps for characters with more than 1 byte per row are
// arranged in row x column order, the second byte in the current row
// will be bytesPerRow after the first byte. So, overall, we start with
// the offset into the array of bitmaps, fontDescriptor[chOffset].offset.
// THEN, because the font bitmaps go from bottom to top, and we're drawing
// from top to bottom, we need to advance our offset out to the final row
// of the bitmap: add bytesPerRow * fontHeight bytes. THEN adjust BACK
// from there to account for the current row. FINALLY we need to adjust for
// how the bytes are arranged in the current row. Whew!
uint8_t bitmap = pgm_read_byte(&fontBitmap[offset - curRow - ((bytesPerRow - curByte - 1) * fontHeight)]);
// Now walk through the bits in the current bitmap byte to see which should be 'on'
// (displayed in textColor) or 'off' (displayed in backgroundColor). Highest-order
// bit is the leftmost pixel in the character.
for (uint8_t curBit = 0, bitSelector = 0x80; curBit < 8; curBit++, bitSelector >>= 1)
{
if (bitmap & bitSelector)
drawPixelInBuffer(x + totalBits, y + curRow, textColor);
else
drawPixelInBuffer(x + totalBits, y + curRow, backgroundColor);
if (totalBits >= chWidth)
break;
totalBits++;
}
}
}
x += chWidth + 1;
}
}
uint8_t getPrintWidth(char *s, const FONT_INFO& fontInfo)
{
const FONT_CHAR_INFO* fontDescriptor = fontInfo.charDesc;
int fontFirstCh = fontInfo.startCh;
uint8_t len, totalWidth = 0;
len = strlen(s);
for(uint8_t i = 0; i < len; i++)
{
totalWidth += pgm_read_byte(&fontDescriptor[s[i] - fontFirstCh].width) + 1;
}
return(totalWidth);
}
// Print a string on the TinyScreen centered horizontally, using the current font.
void printCentered(char *s, uint8_t yOffset, const FONT_INFO& fontInfo, uint16_t textColor, uint16_t backgroundColor)
{
uint8_t printWidth, xOffset;
printWidth = getPrintWidth(s, fontInfo);
xOffset = (SCREEN_WIDTH - printWidth) / 2;
// display.setCursor(xOffset, yOffset);
// display.print(s);
printStringInBuffer(xOffset, yOffset, s, fontInfo, textColor, backgroundColor);
}
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(TSBitDepth16); // 16-bit graphics, none of that puny 8-bit stuff for us!
display.setBrightness(12); // Max brightness all the time!
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);
}
void displayTime()
{
uint16_t red, green, blue, backgroundColor, timeColor;
char s[64];
display.on(); //Turns TinyScreen display on
for (int i = 0; i < 200; i++) // Display for 200 tenth-seconds, look for button press every tenth-second
{
// 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();
setTime(hours,minutes,seconds,days,months,2000 + years); //values in the order hr,min,sec,day,month,year
// Figure out the color based on the current time. Red for hours (5 bits), green for minutes (6 bits), blue for seconds
// (5 bits). Then glom them together into a 16 bit unsigned int: BBBBBGGGGGGRRRRR.
if (colorTimeMap == RGB)
{
red = hours; // 0 - 23, less than 5 full bits (16 + 8 + 4 + 2 + 1 = 31).
green = minutes; // 0 - 59, less than 6 full bits (32 + 16 + 8 + 4 + 2 + 1 = 63).
blue = seconds % 32; // 0 - 59, greater than 5 full bits, so take modulus.
}
else if (colorTimeMap == BRG)
{
blue = hours; // 0 - 23, less than 5 full bits (16 + 8 + 4 + 2 + 1 = 31).
red = minutes % 32; // 0 - 59, greater than 5 full bits, so take modulus.
green = seconds; // 0 - 59, less than 6 full bits (32 + 16 + 8 + 4 + 2 + 1 = 63).
}
else // colorTimeMap == GBR
{
green = hours; // 0 - 23, less than 6 full bits (32 + 16 + 8 + 4 + 2 + 1 = 63).
blue = minutes % 32; // 0 - 59, greater than 5 full bits, so take modulus.
red = seconds % 32; // 0 - 59, greater than 5 full bits, so take modulus.
}
backgroundColor = (blue << 11) | (green << 5) | red;
// Switch from white text to black if any of the color components is really bright.
if ((red >= 24) || (green >= 48) || (blue >= 24))
timeColor = TS_16b_Black;
else
timeColor = TS_16b_White;
//display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, TSRectangleFilled, backgroundColor);
clearScreenBuffer(backgroundColor);
// display.fontColor(timeColor, backgroundColor); //Set the font color, font background
// Print date in US format MM:DD:YY (Switch the order in which day, month, year that you like to use)
// display.setFont(liberationSans_12ptFontInfo); //Set the font type
sprintf(s, "%02d/%02d/20%02d", months, days, years);
printCentered(s, 1, liberationSans_12ptFontInfo, timeColor, backgroundColor);
// printCentered(s, 1);
setTime(hours,minutes,seconds,days,months,2000 + years); //values in the order hr,min,sec,day,month,year
strcpy(s, dayStr(weekday()));
printCentered(s, 17, liberationSans_12ptFontInfo, timeColor, backgroundColor);
// printCentered(s, 17);
// display time in HH:MM:SS 24 hour format
display.setFont(liberationSans_16ptFontInfo); //Set the font type
sprintf(s, "%02d:%02d:%02d", hours, minutes, seconds);
printCentered(s, 35, liberationSans_16ptFontInfo, timeColor, backgroundColor);
// printCentered(s, 35);
// Display the RGB info
display.setFont(thinPixel7_10ptFontInfo); //Set the font type
if (colorTimeMap == RGB)
sprintf(s, "R%02d-G%02d-B%02d", red, green, blue);
else if (colorTimeMap == BRG)
sprintf(s, "B%02d-R%02d-G%02d", blue, red, green);
else
sprintf(s, "G%02d-B%02d-R%02d", green, blue, red);
printCentered(s, 55, thinPixel7_10ptFontInfo, timeColor, backgroundColor);
// printCentered(s, 55);
sendBufferToScreen();
delay(95); //display for ~0.1 seconds
if (display.getButtons() & TSButtonUpperRight)
{
rotateRGB();
i = 0;
}
}
display.off(); //Turns TinyScreen display off
}
void rotateRGB()
{
if (colorTimeMap == RGB)
colorTimeMap = BRG;
else if (colorTimeMap == BRG)
colorTimeMap = GBR;
else
colorTimeMap = RGB;
}
void loop()
{
uint8_t buttons;
if ((buttons = display.getButtons()))
{
if (buttons & TSButtonUpperRight)
rotateRGB();
displayTime();
}
}
Comments