Arduino Clock with Neopixel Ring Animation
Yet another useless Arduino clock, but i love it.
- 73,546 views
- 46 comments
- 107 respects
Components and supplies
Necessary tools and machines
![]() |
|
Apps and online services
About this project
According to Larry Wall there are three great virtues of a programmer: Laziness, Impatience and Hubris. I have all of them. Unfortunately my programs are not the treasure. So what should I do? use the Arduino. It is so small to store all my bugs within.
The neopixel ring was created by great men I believe. It is so beautiful and excellent organized inside I cannot stay aside of it. This is my first arduino project that use this ring.
The other thing is that 0.56" 4 segment indicator excellent fits to the neopixel ring with 24 LEDs.
The project has 6 different algorithms to animate the clock. They switched randomly depends of the current time. The ring led color changes randomly too.
DS3231 RTC module have a great stability and accuracy!
Updated sketch has new features:
- you can tailor morning and evening times which defines the neopixel animation period;
- light-mode alarm now is available in the clock. Neopixel ring is simulating sunrise during 5 minutes. The alarm has weekday scheduling. You can select several presets: once, work days, Monday through Thursday or everyday; OR you can manually setup alarm day mask (in HEX, 0-th bit is Sunday, 6-th bit is Saturday).
- night-light can be lit after evening time. It is some fireplace simulation. Number of the led that are lit in this mode can be configured.
Here you can find the EasyEDA PCB project of the clock.
You can use the PCB to build the project in the following order:
- Solder all SMD components
- Solder both ICs, Atmega328 and shift register
- Solder 220 ohm resistors. If you use green led display, you can use another resistors, like 100 ohm.
- Solder battery case
- Solder power, uart and button sockets
- Solder 4 digits 7-segment LED display
- Place the clock to your case
- Solder the neopixel ring
Code
//The clock with 4x7-segment indicator with a shift register and neopixel rgb 24 led ring @ arduino nano
#include <EEPROM.h>
#include <Wire.h>
#include <DS3232RTC.h>
#include <Time.h>
#include <TimeLib.h>
// The 4 7-segment indicator with the shift register
const byte digit_pin[4] = { 14, 15, 16, 17 };
const byte dataPIN_14 = 7;
const byte latchPIN_12 = 8;
const byte clockPIN_11 = 9;
// Neopixel ring
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
const byte NEOPIXEL = 10; // Pin of Neopixel Ring
const byte RingSize = 24;
const byte HZ = 10; // Redraw neopixel ring frequency (times per second)
const byte BTN_MENU_PIN = 2;
const byte BTN_INCR_PIN = 3;
Adafruit_NeoPixel strip = Adafruit_NeoPixel(RingSize, NEOPIXEL, NEO_GRB + NEO_KHZ800);
//------------------------------------------ Configuration data ------------------------------------------------
/* Config record in the EEPROM has the following format:
uint32_t ID each time increment by 1
struct cfg config data
byte CRC the checksum
*/
struct cfg {
uint16_t alarm_time; // Alarm time in minuites from midnight
byte wday_mask; // Week day alarm mask 0 - sunday, 7 - saturday
bool active; // Whether the alarm is active
byte nl_br; // The night light brightness 0 - off, [1 - 6]
byte morning; // Morning time (10-minutes intervals), startinf neopixel animation
byte evening; // Evening time (10-minutes)
};
class CONFIG {
public:
CONFIG() {
can_write = false;
buffRecords = 0;
rAddr = wAddr = 0;
eLength = 0;
nextRecID = 0;
byte rs = sizeof(struct cfg) + 5; // The total config record size
// Select appropriate record size; The record size should be power of 2, i.e. 8, 16, 32, 64, ... bytes
for (record_size = 8; record_size < rs; record_size <<= 1);
}
void init();
void load(void);
void getConfig(struct cfg &Cfg); // Copy config structure from this class
void updateConfig(struct cfg &Cfg); // Copy updated config into this class
bool save(void); // Save current config copy to the EEPROM
bool saveConfig(struct cfg &Cfg); // write updated config into the EEPROM
private:
void defaultConfig(void);
struct cfg Config;
bool readRecord(uint16_t addr, uint32_t &recID);
bool can_write; // The flag indicates that data can be saved
byte buffRecords; // Number of the records in the outpt buffer
uint16_t rAddr; // Address of thecorrect record in EEPROM to be read
uint16_t wAddr; // Address in the EEPROM to start write new record
uint16_t eLength; // Length of the EEPROM, depends on arduino model
uint32_t nextRecID; // next record ID
byte record_size; // The size of one record in bytes
};
// Read the records until the last one, point wAddr (write address) after the last record
void CONFIG::init(void) {
eLength = EEPROM.length();
uint32_t recID;
uint32_t minRecID = 0xffffffff;
uint16_t minRecAddr = 0;
uint32_t maxRecID = 0;
uint16_t maxRecAddr = 0;
byte records = 0;
nextRecID = 0;
// read all the records in the EEPROM find min and max record ID
for (uint16_t addr = 0; addr < eLength; addr += record_size) {
if (readRecord(addr, recID)) {
++records;
if (minRecID > recID) {
minRecID = recID;
minRecAddr = addr;
}
if (maxRecID < recID) {
maxRecID = recID;
maxRecAddr = addr;
}
} else {
break;
}
}
if (records == 0) {
wAddr = rAddr = 0;
can_write = true;
return;
}
rAddr = maxRecAddr;
if (records < (eLength / record_size)) { // The EEPROM is not full
wAddr = rAddr + record_size;
if (wAddr > eLength) wAddr = 0;
} else {
wAddr = minRecAddr;
}
can_write = true;
}
void CONFIG::getConfig(struct cfg &Cfg) {
memcpy(&Cfg, &Config, sizeof(struct cfg));
}
void CONFIG::updateConfig(struct cfg &Cfg) {
memcpy(&Config, &Cfg, sizeof(struct cfg));
}
bool CONFIG::saveConfig(struct cfg &Cfg) {
updateConfig(Cfg);
return save(); // Save new data into the EEPROM
}
bool CONFIG::save(void) {
if (!can_write) return can_write;
if (nextRecID == 0) nextRecID = 1;
uint16_t startWrite = wAddr;
uint32_t nxt = nextRecID;
byte summ = 0;
for (byte i = 0; i < 4; ++i) {
EEPROM.write(startWrite++, nxt & 0xff);
summ <<=2; summ += nxt;
nxt >>= 8;
}
byte* p = (byte *)&Config;
for (byte i = 0; i < sizeof(struct cfg); ++i) {
summ <<= 2; summ += p[i];
EEPROM.write(startWrite++, p[i]);
}
summ ++; // To avoid empty records
EEPROM.write(wAddr+record_size-1, summ);
rAddr = wAddr;
wAddr += record_size;
if (wAddr > EEPROM.length()) wAddr = 0;
return true;
}
void CONFIG::load(void) {
bool is_valid = readRecord(rAddr, nextRecID);
nextRecID ++;
if (!is_valid) defaultConfig();
return;
}
bool CONFIG::readRecord(uint16_t addr, uint32_t &recID) {
byte Buff[record_size];
for (byte i = 0; i < record_size; ++i)
Buff[i] = EEPROM.read(addr+i);
byte summ = 0;
for (byte i = 0; i < sizeof(struct cfg) + 4; ++i) {
summ <<= 2; summ += Buff[i];
}
summ ++; // To avoid empty fields
if (summ == Buff[record_size-1]) { // Checksumm is correct
uint32_t ts = 0;
for (char i = 3; i >= 0; --i) {
ts <<= 8;
ts |= Buff[byte(i)];
}
recID = ts;
memcpy(&Config, &Buff[4], sizeof(struct cfg));
return true;
}
return false;
}
void CONFIG::defaultConfig(void) {
Config.alarm_time = 0;
Config.wday_mask = 0;
Config.active = false;
Config.nl_br = 3;
Config.morning = 60; // 10:00
Config.evening = 126; // 21:00
}
//------------------------------------------ class BUTTON ------------------------------------------------------
class BUTTON {
public:
BUTTON(byte ButtonPIN, uint16_t timeout_ms = 3000) {
buttonPIN = ButtonPIN;
pt = tickTime = 0;
overPress = timeout_ms;
}
void init(void) { pinMode(buttonPIN, INPUT_PULLUP); }
void setTimeout(uint16_t timeout_ms = 3000) { overPress = timeout_ms; }
byte buttonCheck(void);
byte intButtonStatus(void) { byte m = mode; mode = 0; return m; }
bool buttonTick(void);
void buttonCnangeINTR(void);
private:
const uint16_t shortPress = 900; // If the button was pressed less that this timeout, we assume the short button press
const uint16_t tickTimeout = 200; // Period of button tick, while tha button is pressed
const byte bounce = 50; // Bouncing timeout (ms)
uint16_t overPress; // Maxumum time in ms the button can be pressed
volatile byte mode; // The button mode: 0 - not presses, 1 - short press, 2 - long press
volatile uint32_t pt; // Time in ms when the button was pressed (press time)
volatile uint32_t tickTime; // The time in ms when the button Tick was set
byte buttonPIN; // The pin number connected to the button
};
byte BUTTON::buttonCheck(void) { // Check the button state, called each time in the main loop
mode = 0;
bool keyUp = digitalRead(buttonPIN); // Read the current state of the button
uint32_t now_t = millis();
if (!keyUp) { // The button is pressed
if ((pt == 0) || (now_t - pt > overPress)) pt = now_t;
} else {
if (pt == 0) return 0;
if ((now_t - pt) < bounce) return 0;
if ((now_t - pt) > shortPress) // Long press
mode = 2;
else
mode = 1;
pt = 0;
}
return mode;
}
bool BUTTON::buttonTick(void) { // When the button pressed for a while, generate periodical ticks
bool keyUp = digitalRead(buttonPIN); // Read the current state of the button
uint32_t now_t = millis();
if (!keyUp && (now_t - pt > shortPress)) { // The button have been pressed for a while
if (now_t - tickTime > tickTimeout) {
tickTime = now_t;
return (pt != 0);
}
} else {
if (pt == 0) return false;
tickTime = 0;
}
return false;
}
void BUTTON::buttonCnangeINTR(void) { // Interrupt function, called when the button status changed
bool keyUp = digitalRead(buttonPIN);
unsigned long now_t = millis();
if (!keyUp) { // The button has been pressed
if ((pt == 0) || (now_t - pt > overPress)) pt = now_t;
} else {
if ((now_t - pt) < bounce) return;
if (pt > 0) {
if ((now_t - pt) < shortPress) mode = 1; // short press
else mode = 2; // long press
pt = 0;
}
}
}
//------------------------------------------ class LED display with the shift register -------------------------
class DSPL {
public:
DSPL(const byte data, const byte latch, const byte clck, const byte dgts[4]) {
dataPIN = data;
latchPIN = latch;
clockPIN = clck;
for (byte i = 0; i < 4; ++i) digit_pin[i] = dgts[i];
}
void init(void); // Initialize the clock display
void show(byte dot, byte displayMask = 0xf); // Should be called periodicaly to redraw the data on the LED display
void showTime(byte Hour, byte Minute); // Set time to the display
void showDash(void); // show dash line if the alarm is not setup
void showWdayGeneral(byte wday_mask);
void showWdayFull(byte wday_mask);
private:
byte dataPIN, latchPIN, clockPIN; // The shift register interface pins
byte digit_pin[4]; // The pin of segmants
byte symbol[4]; // The symbols to be displyed
const byte hex[16] = { 0b11111100, 0b01100000, 0b11011010, 0b11110010, 0b01100110,
0b10110110, 0b10111110, 0b11100000, 0b11111110, 0b11110110,
0b11101110, 0b00111110, 0b10011100, 0b01111010, 0b10011110, 0b10001110};
};
const byte wday[7] = { 0b00000100, 0b10000000, 0b01000000, 0b00000010, 0b00100000, 0b00010000, 0b00001000 };
void DSPL::init(void) {
pinMode(dataPIN, OUTPUT);
pinMode(latchPIN, OUTPUT);
pinMode(clockPIN, OUTPUT);
for (int i = 0; i < 4; ++i) {
pinMode(digit_pin[i], OUTPUT);
digitalWrite(digit_pin[i], HIGH);
}
}
void DSPL::show(byte dot, byte displayMask) {
byte mask = 1;
for (byte d = 0; d < 4; ++d) { // Print out digit from right to left
byte s = symbol[d];
if (dot & mask) s |= 1;
if (displayMask & mask) {
digitalWrite(latchPIN_12, LOW);
shiftOut(dataPIN_14, clockPIN_11, LSBFIRST, s);
digitalWrite(latchPIN_12, HIGH);
digitalWrite(digit_pin[d], LOW);
delayMicroseconds(50);
digitalWrite(digit_pin[d], HIGH);
}
mask <<= 1;
}
}
void DSPL::showTime(byte Hour, byte Minute) {
byte digit;
for (byte d = 0; d < 4; ++d) { // Print out digit from right to left
if (d < 2) { // Minutes
digit = Minute % 10;
Minute /= 10;
} else { // Hours
digit = Hour % 10;
Hour /= 10;
}
symbol[d] = hex[digit];
}
}
void DSPL::showDash(void) {
for (byte d = 0; d < 4; ++d)
symbol[d] = 0b00000010; // dash
}
void DSPL::showWdayGeneral(byte wday_mask) {
byte wd = 0;
byte m = 1;
symbol[3] = hex[10] | 1; // 'A.'
switch (wday_mask) {
case 0b00111110: // Working days
symbol[2] = hex[1];
symbol[1] = 0b00000010; // dash
symbol[0] = hex[5];
break;
case 0b00011110:
symbol[2] = hex[1];
symbol[1] = 0b00000010; // dash
symbol[0] = hex[4];
break;
case 0b01111111:
symbol[2] = hex[10]; // A
symbol[1] = symbol[0] = 0b00011100; // L
break;
case 0:
showDash();
default: // 0 or unknown
for (byte i = 0; i <= 6; ++i) {
if (m & wday_mask) wd |= wday[i];
m <<= 1;
}
symbol[2] = wd;
break;
}
}
void DSPL::showWdayFull(byte wday_mask) {
symbol[3] = hex[10] | 1; // 'A.'
symbol[2] = hex[wday_mask / 16];
symbol[1] = hex[wday_mask % 16] | 1;
byte wd = 0;
byte m = 1;
for (byte i = 0; i <= 6; ++i) {
if (m & wday_mask) wd |= wday[i];
m <<= 1;
}
symbol[0] = wd;
}
//------------------------------------------ class NEOPIXEL ANIMATION ------------------------------------------
class ANIMATION {
public:
ANIMATION() {
for (byte i = 0; i < 3; ++i)
rgb[i] = random(32) << 2;
}
void switchOffRing(void);
uint32_t Wheel(byte WheelPos);
virtual void show(uint16_t S, uint32_t Color) = 0;
virtual void clean(uint16_t S, uint32_t Color) = 0;
virtual void handle(byte k) { }
protected:
void flashLabels(uint16_t S, byte pos = 255);
void fadeRing(uint32_t Color, byte shift);
void initXcolor(void); // Initialize the color of cross-clock pixels
byte rgb[3]; // The color of the cross-clock pixels: 0h, 3h, 6h, 9h
};
void ANIMATION::switchOffRing(void) {
for (byte i = 0; i < strip.numPixels(); ++i) strip.setPixelColor(i, 0);
strip.show();
}
// 4 indicators (0h, 3h, 6h, 9h) is flashing every 5 seconds
void ANIMATION::flashLabels(uint16_t S, byte pos) {
static byte br_lev[] = { 127, 64, 50, 40, 30, 25, 30, 40, 50, 64 };
const uint16_t tick = 5 * HZ; // ticks in 5 seconds
byte secPart = S % tick;
byte divider = tick / 10; // Normalize time interval to the array size
byte pos1 = secPart / divider; // index in the brightness array
byte k = secPart % divider;
byte pos2 = pos1 + 1; if (pos2 >= 10) pos2 = 0;
byte br = map(k, 0, divider, br_lev[pos1], br_lev[pos2]);
int r = int(rgb[0]) * br; r >>= 7;
int g = int(rgb[1]) * br; g >>= 7;
int b = int(rgb[3]) * br; b >>= 7;
uint32_t white = strip.Color(r, g, b);
int n = strip.numPixels();
for (byte label = 0; label < n; label += n/4) {
if (label == 0 || label != pos) strip.setPixelColor(label, white);
}
}
uint32_t ANIMATION:: Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if (WheelPos < 85) {
return strip.Color((255 - WheelPos * 3) >> 3, 0, (WheelPos * 3) >> 3);
}
if(WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, (WheelPos * 3) >> 3, (255 - WheelPos * 3) >> 3);
}
WheelPos -= 170;
return strip.Color((WheelPos * 3) >> 3, (255 - WheelPos * 3) >> 3, 0);
}
void ANIMATION::fadeRing(uint32_t Color, byte shift) {
byte b = Color;
byte g = Color >> 8;
byte r = Color >> 16;
r >>= shift;
g >>= shift;
b >>= shift;
uint32_t Color_faded = strip.Color(r, g, b);
for (byte i = 0; i < strip.numPixels(); ++i) strip.setPixelColor(i, Color_faded);
}
void ANIMATION::initXcolor(void) {
for (byte i = 0; i < 3; ++i)
rgb[i] = random(32) << 2;
}
//------------------------------ Animation: Fill ring as seconds past, new dot every 2.5 sec -------------------
class FillRing : public ANIMATION {
public:
FillRing() { old_pos = 0; }
virtual void show(uint16_t S, uint32_t Color);
virtual void clean(uint16_t S, uint32_t Color);
private:
byte old_pos; // Last position filed with new color
};
void FillRing::show(uint16_t S, uint32_t Color) {
if (S == 0) initXcolor();
if (S < RingSize) strip.setPixelColor(S, 0); // Clear the ring in the beginning of the new minutes
byte pos = (uint32_t)S * RingSize / ((uint32_t)HZ * 60);
if (pos != old_pos) {
strip.setPixelColor(pos, Color);
old_pos = pos;
}
flashLabels(S, pos);
}
void FillRing::clean(uint16_t S, uint32_t Color) {
const uint16_t Last8Ticks = 60 * HZ - 8;
if (S > Last8Ticks) {
S -= Last8Ticks;
fadeRing(Color, S);
flashLabels(S);
} else {
show(S, Color);
}
}
//------------------------------ Animation: Each dot is coming counterclockwize from 12 o'clock ----------------
class cClockRing : public ANIMATION {
public:
cClockRing() { run_pos = 255; }
virtual void show(uint16_t S, uint32_t Color);
virtual void clean(uint16_t S, uint32_t Color);
private:
byte run_pos; // Last position filed with new color
};
void cClockRing::show(uint16_t S, uint32_t Color) {
const byte onedot = ((uint16_t)HZ * 60) / RingSize;
if (S == 0) initXcolor();
byte pos = (uint32_t)S * RingSize / ((uint32_t)HZ * 60);
byte runSecond = S % onedot;
byte newRunPos = RingSize - RingSize * runSecond / onedot;
if ((newRunPos < RingSize) && newRunPos > pos) {
if (newRunPos != run_pos) {
strip.setPixelColor(newRunPos, Color);
if (run_pos < RingSize) strip.setPixelColor(run_pos, 0);
run_pos = newRunPos;
}
}
strip.setPixelColor(pos, Color);
flashLabels(S, run_pos);
}
void cClockRing::clean(uint16_t S, uint32_t Color) {
const uint16_t Last8Ticks = 60 * HZ - 8;
if (S > Last8Ticks) {
S -= Last8Ticks;
fadeRing(Color, S);
flashLabels(S);
} else {
show(S, Color);
}
}
//------------------------------ Animation: Each dot dot is coming counterclockwize from last position ---------
class lpClockRing : public ANIMATION {
public:
lpClockRing() { run_pos = 255; }
virtual void show(uint16_t S, uint32_t Color);
virtual void clean(uint16_t S, uint32_t Color);
private:
byte run_pos; // Last position filed with new color
};
void lpClockRing::show(uint16_t S, uint32_t Color) {
const byte onedot = ((uint16_t)HZ * 60) / RingSize;
if (S == 0) initXcolor();
byte pos = uint32_t(S) * RingSize / (uint32_t(HZ) * 60);
byte runSecond = S % onedot;
char newRunPos = pos - RingSize * runSecond / onedot;
if (newRunPos < 0) newRunPos += RingSize;
if ((newRunPos < RingSize) && newRunPos != pos) {
if (newRunPos != run_pos) {
strip.setPixelColor(newRunPos, Color);
if (run_pos < RingSize) strip.setPixelColor(run_pos, 0);
run_pos = newRunPos;
}
}
strip.setPixelColor(pos, Color);
flashLabels(S, pos);
}
void lpClockRing::clean(uint16_t S, uint32_t Color) {
const uint16_t LastTick = 60 * HZ - 1;
show(S, Color);
if (S >= LastTick) strip.setPixelColor(RingSize-1, 0);
}
//------------------------------ Animation: Rise the dot slowly ------------------------------------------------
class rsRing : public ANIMATION {
public:
rsRing() { keep_pos = true; }
virtual void show(uint16_t S, uint32_t Color);
virtual void clean(uint16_t S, uint32_t Color);
virtual void handle(byte k) { keep_pos = (k != 0); }
private:
bool keep_pos;
};
void rsRing::show(uint16_t S, uint32_t Color) {
const byte onedot = ((uint16_t)HZ * 60) / RingSize;
if (S == 0) initXcolor();
if (S < RingSize) strip.setPixelColor(S, 0); // Clear the ring in the beginning of the new minutes
byte pos = (uint32_t)S * RingSize / ((uint32_t)HZ * 60);
byte b1 = Color;
byte g1 = Color >> 8;
byte r1 = Color >> 16;
byte runSecond = S % onedot;
byte shift = (runSecond << 3) / onedot;
r1 >>= (7 - shift);
g1 >>= (7 - shift);
b1 >>= (7 - shift);
strip.setPixelColor(pos, strip.Color(r1, g1, b1));
if (!keep_pos) {
byte fadePos = RingSize-1;
if (pos >= 1) fadePos = pos - 1;
uint32_t fadeColor = strip.getPixelColor(fadePos);
b1 = fadeColor;
g1 = fadeColor >> 8;
r1 = fadeColor >> 16;
if (shift > 3) {
r1 >>= 1;
g1 >>= 1;
b1 >>= 1;
} else if (shift == 7) strip.setPixelColor(fadePos, 0);
strip.setPixelColor(fadePos, strip.Color(r1, g1, b1));
}
flashLabels(S, pos);
}
void rsRing::clean(uint16_t S, uint32_t Color) {
const byte onedot = ((uint16_t)HZ * 60) / RingSize;
const uint16_t Last8Ticks = 60 * HZ - 8;
show(S, Color);
byte runSecond = S % onedot;
byte shift = (runSecond << 3) / onedot;
if (S > Last8Ticks) {
if (keep_pos) fadeRing(Color, S);
if (!keep_pos && (shift == 7)) strip.setPixelColor(RingSize-1, 0);
}
}
//------------------------------ Animation: Run the sector clockwise -------------------------------------------
class rSectRing : public ANIMATION {
public:
rSectRing() { firstPos = 255; }
virtual void show(uint16_t S, uint32_t Color);
virtual void clean(uint16_t S, uint32_t Color);
private:
byte firstPos;
};
void rSectRing::show(uint16_t S, uint32_t Color) {
const byte onedot = ((uint16_t)HZ * 60) / RingSize;
if (S == 0) initXcolor();
byte length = (uint32_t)S * RingSize / ((uint32_t)HZ * 60) + 1;
byte runSecond = S % onedot;
byte newFirstPos = RingSize * runSecond / onedot;
char lastPos = newFirstPos - length;
if (lastPos < 0) lastPos += RingSize;
if (newFirstPos < RingSize) {
if (newFirstPos != firstPos) {
strip.setPixelColor(newFirstPos, Color);
if (lastPos >= 0) strip.setPixelColor(lastPos, 0);
firstPos = newFirstPos;
}
}
flashLabels(S, firstPos);
}
void rSectRing::clean(uint16_t S, uint32_t Color) {
const uint16_t Last8Ticks = 60 * HZ - 8;
if (S == 0) initXcolor();
if (S > Last8Ticks) {
S -= Last8Ticks;
fadeRing(Color, S);
flashLabels(S);
} else {
show(S, Color);
}
}
//------------------------------ Animation: Swing the sector ---------------------------------------------------
class swingRing : public ANIMATION {
public:
swingRing() { firstPos = 255; }
virtual void show(uint16_t S, uint32_t Color);
virtual void clean(uint16_t S, uint32_t Color);
private:
byte firstPos;
};
void swingRing::show(uint16_t S, uint32_t Color) {
const byte onedot = ((uint16_t)HZ * 60) / RingSize;
if (S == 0) initXcolor();
byte length = (uint32_t)S * RingSize / ((uint32_t)HZ * 60) + 1;
byte runSecond = S % onedot;
byte newFirstPos;
char lastPos;
if ((length % 2) == 0) { // even
newFirstPos = RingSize - RingSize * runSecond / onedot;
lastPos = newFirstPos + length;
if (lastPos >= RingSize) lastPos = -1;
} else { // odd
newFirstPos = RingSize * runSecond / onedot;
lastPos = newFirstPos - length;
if (lastPos < 0) lastPos = -1;
}
if (newFirstPos < RingSize) {
if (newFirstPos != firstPos) {
strip.setPixelColor(newFirstPos, Color);
if ((lastPos >= 0) && (lastPos < RingSize))
strip.setPixelColor(lastPos, 0);
firstPos = newFirstPos;
}
}
flashLabels(S, firstPos);
}
void swingRing::clean(uint16_t S, uint32_t Color) {
const uint16_t Last8Ticks = 60 * HZ - 8;
if (S > Last8Ticks) {
S -= Last8Ticks;
fadeRing(Color, S);
flashLabels(S);
} else {
show(S, Color);
}
}
//------------------------------ Animation: Fill ring wth the rainbow as seconds past --------------------------
class fRainRing : public ANIMATION {
public:
fRainRing() { firstPos = 255; }
virtual void show(uint16_t S, uint32_t Color);
virtual void clean(uint16_t S, uint32_t Color);
private:
byte firstPos;
};
void fRainRing::show(uint16_t S, uint32_t Color) {
if (S == 0) initXcolor();
if (S < RingSize) strip.setPixelColor(S, 0); // Clear the ring in the beginning of the new minutes
for(uint16_t i = 1; i <= RingSize; ++i) {
strip.setPixelColor(RingSize - i, Wheel(i+S));
}
flashLabels(S);
}
void fRainRing::clean(uint16_t S, uint32_t Color) {
const uint16_t LastRSTicks = 60 * HZ - RingSize;
if (S > LastRSTicks) {
S -= LastRSTicks;
strip.setPixelColor(S, 0);
} else {
show(S, Color);
}
}
//------------------------------ Animation: Fill ring wth the another rainbow as seconds past ------------------
class aRainRing : public ANIMATION {
public:
aRainRing() { firstPos = 255; }
virtual void show(uint16_t S, uint32_t Color);
virtual void clean(uint16_t S, uint32_t Color);
private:
byte firstPos;
};
void aRainRing::show(uint16_t S, uint32_t Color) {
if (S == 0) initXcolor();
if (S < RingSize) strip.setPixelColor(S, 0); // Clear the ring in the beginning of the new minutes
for(uint16_t i = 1; i <= RingSize; ++i) {
strip.setPixelColor(RingSize - i, Wheel(((i * 256 / RingSize) + S) & 255));
}
flashLabels(S);
}
void aRainRing::clean(uint16_t S, uint32_t Color) {
const uint16_t LastRSTicks = 60 * HZ - RingSize;
if (S > LastRSTicks) {
S -= LastRSTicks;
strip.setPixelColor(S, 0);
} else {
show(S, Color);
}
}
//------------------------------ Animation: Wake-up signal, sunrise --------------------------------------------
class sunRise : public ANIMATION {
public:
sunRise() { loop_number = 0; started = false; for (byte i = 0; i < 3; rgb[i++] = 0); }
virtual void show(uint16_t S, uint32_t Color);
virtual void clean(uint16_t S, uint32_t Color);
virtual void handle(byte k) { loop_number = 0; started = false; for (byte i = 0; i < 3; rgb[i++] = 0); }
private:
uint16_t loop_number; // [0 - 4]
bool started; // Whether the sequence started correctly
const byte rise[6][3] = {{0, 0, 0}, {33, 2, 0}, {66, 6, 2}, {99, 18, 3}, {133, 24, 4}, {255, 50, 50}};
};
void sunRise::show(uint16_t S, uint32_t Color) {
if (!started) {
if (S > 300) return; // Do not start the sequence at the end of minute
started = true;
}
byte rb = map(uint32_t(loop_number) * 600 + S, 0, 1800, 1, RingSize/2);
rb = constrain(rb, 1, RingSize/2);
byte lb = constrain(RingSize-rb, RingSize/2+1, RingSize-1);
for (byte i = 0; i < 3; ++i)
rgb[i] = map(S, 0, 600, rise[loop_number][i], rise[loop_number+1][i]);
uint32_t c = strip.Color(rgb[0], rgb[1], rgb[2]);
for (byte i = RingSize-1; i >= lb; --i) strip.setPixelColor(i, c);
for (byte i = 0; i <= rb; ++i) strip.setPixelColor(i, c);
if (S == 599) {
if (loop_number < 4) ++loop_number;
}
}
void sunRise::clean(uint16_t S, uint32_t Color) {
const uint16_t LastRSTicks = 60 * HZ - RingSize;
if (S > LastRSTicks) {
S -= LastRSTicks;
strip.setPixelColor(S, 0);
} else {
show(S, Color);
}
}
//------------------------------ Animation: night light, fireplace ---------------------------------------------
class firePlace : public ANIMATION {
public:
firePlace() { factor = 1; indx = 0; }
virtual void show(uint16_t S, uint32_t Color);
virtual void clean(uint16_t S, uint32_t Color);
virtual void handle(byte k) { factor = constrain(k, 1, RingSize/2); indx = factor - 1; }
private:
void addColor(uint8_t position, uint32_t color);
void substractColor(uint8_t position, uint32_t color);
uint32_t blend(uint32_t color1, uint32_t color2);
uint32_t substract(uint32_t color1, uint32_t color2);
int factor; // Only pixels divided by the factor are lit
int indx; // Index of the first pixel
const uint32_t fire_color = 0x050200;
};
void firePlace::show(uint16_t S, uint32_t Color) {
if ((S == 599) && (--indx < 0)) indx = factor-1; // shift lit pixels every minute
if (S % random(1, 4)) return;
for (byte i = 0; i < RingSize; ++i) {
strip.setPixelColor(i, 0);
if (((i + indx) % factor) == 0) { // Tune the brightness by the factor value
addColor(i, fire_color);
byte r = random(3);
uint32_t diff_color = strip.Color(r, r/2, r/2);
substractColor(i, diff_color);
}
}
}
void firePlace::clean(uint16_t S, uint32_t Color) {
const uint16_t LastRSTicks = 60 * HZ - RingSize;
if (S > LastRSTicks) {
S -= LastRSTicks;
strip.setPixelColor(S, 0);
} else {
show(S, Color);
}
}
void firePlace::addColor(uint8_t position, uint32_t color) {
uint32_t blended_color = blend(strip.getPixelColor(position), color);
strip.setPixelColor(position, blended_color);
}
void firePlace::substractColor(uint8_t position, uint32_t color) {
uint32_t blended_color = substract(strip.getPixelColor(position), color);
strip.setPixelColor(position, blended_color);
}
uint32_t firePlace::blend(uint32_t color1, uint32_t color2) {
byte r1,g1,b1;
byte r2,g2,b2;
r1 = (byte)(color1 >> 16),
g1 = (byte)(color1 >> 8),
b1 = (byte)(color1 >> 0);
r2 = (byte)(color2 >> 16),
g2 = (byte)(color2 >> 8),
b2 = (byte)(color2 >> 0);
return strip.Color(constrain(r1+r2, 0, 255), constrain(g1+g2, 0, 255), constrain(b1+b2, 0, 255));
}
uint32_t firePlace::substract(uint32_t color1, uint32_t color2) {
byte r1,g1,b1;
byte r2,g2,b2;
int16_t r,g,b;
r1 = (byte)(color1 >> 16),
g1 = (byte)(color1 >> 8),
b1 = (byte)(color1 >> 0);
r2 = (byte)(color2 >> 16),
g2 = (byte)(color2 >> 8),
b2 = (byte)(color2 >> 0);
r = (int16_t)r1 - (int16_t)r2;
g = (int16_t)g1 - (int16_t)g2;
b = (int16_t)b1 - (int16_t)b2;
if(r < 0) r = 0;
if(g < 0) g = 0;
if(b < 0) b = 0;
return strip.Color(r, g, b);
}
//------------------------------------------ class ALARM -------------------------------------------------------
class ALARM {
public:
ALARM() { }
void init(bool act, uint16_t minutes = 0, byte wmask = 0);
void activate(bool a) { if ((active = a)) calculateAlarmTime(); }
bool isActive(void) { return active; }
byte wdayMask(void) { return wday_mask; }
void calculateAlarmTime(bool next = false);// Calculate the next alarm time; in next, calculate the next alarm time in one minute from the current time
bool isAlarmNow(void); // Whether the alarm should be started
void stopAlarm(void) { firing = false; }
time_t alarmTime(void) { return next_alarm; }
private:
time_t next_alarm; // The UNIX time of the next alarm
time_t stop_time; // The time when to stop active alarm
uint16_t alarm_time; // The minutes from midnight to fire the alarm
byte wday_mask; // The week day bitmask to fire the alarm 0 - sunday, 7 saturday
bool active; // Whether the alarm is active
bool firing; // Whether tha alarm is firing right now
};
void ALARM::init(bool act, uint16_t minutes, byte wmask) {
active = act;
if (minutes >= 24*60) minutes = 24*60-1;
alarm_time = minutes;
wday_mask = wmask & 0b01111111;
if (active) calculateAlarmTime();
}
bool ALARM::isAlarmNow(void) {
time_t n = now();
if (firing) {
if (n >= stop_time) { // The time to stop alarm
firing = false;
stop_time = 0;
}
return firing;
}
if (!active) return firing; // There is no active alarm
byte S = n % 60;
if (!firing && (S <= 10) && (n - next_alarm) <= 10) {
firing = true; // It is Time to fire the alarm
stop_time = next_alarm + 300; // Set the time to stop alarm firing (5 minutes)
calculateAlarmTime(true);
}
return firing;
}
void ALARM::calculateAlarmTime(bool next) {
tmElements_t tm;
next_alarm = 0;
if (!active) return;
time_t n = RTC.get(); // RTC.read load weekday incorectly
if (next) n += 60; // Calculate next alarm in one minute of the current time
breakTime(n, tm);
tm.Second = 0;
long dm = long(tm.Hour) * 60 + tm.Minute; // The time in minutes since midnight
long delta_minutes = long(alarm_time) - dm;
byte delta_days = 0;
if (wday_mask) { // This alarm should be repeated some week days
byte m = 1;
delta_days = 8;
for (byte j = 0; j < 7; ++j) {
if (wday_mask & m) { // Fire the alarm in j - day
char d = j + 1 - tm.Wday; // sunday is 1
if (d < 0) d += 7;
if ((d == 0) && (delta_minutes <= 0)) d = 7; // Today is late for this alarm, we should fire it in a week
...
This file has been truncated, please download it to see its full contents.
Schematics
The schematics

Comments
Author
Published on
May 7, 2016Members who respect this project
you might like