/*
* Flip Clock - 7/27/14 - October 2014
* August - menuing system; RTC; temperature
sensor; rotary encoder
*/
#include
"Wire.h" //
#include
"OneWire.h" //
DS18S20 Temperature chip i/o from
http://www.pjrc.com/teensy/td_libs_OneWire.html
#include
"RealTimeClockDS1307.h" //
https://github.com/davidhbrown/RealTimeClockDS1307
#include
"LiquidCrystal_I2C.h" //
http://arduino-info.wikispaces.com/LCD-Blue-I2C
#include
"avr/pgmspace.h" //
for flash memory storage
#include
"EEPROM.h" //
for eeprom storage
#include
"Servo.h" //
for servo motor
LiquidCrystal_I2C
lcd(0x27,20,4); // set the LCD address
to 0x27 for a 16 chars and 4 line display
OneWire ds(9); //
DS18B20 Temperature chip i/o on pin 9 - OneWire protocol
// Flash Storage: 20
chrs max 12345678901234567890 -
these go into flash (program) memory
prog_char
menuItem_0[] PROGMEM =
"Chime"; //menu items start
here
prog_char
menuItem_1[] PROGMEM = "Quarter
chime";
prog_char
menuItem_2[] PROGMEM = "Half hour
chime";
prog_char
menuItem_3[] PROGMEM = "Hourly
chime style";
prog_char
menuItem_4[] PROGMEM = "Set sleep
time";
prog_char
menuItem_5[] PROGMEM = "Set wake
time";
prog_char
menuItem_6[] PROGMEM = "Set chime
sleep";
prog_char
menuItem_7[] PROGMEM = "Set chime
wake";
prog_char
menuItem_8[] PROGMEM = "Show
temp";
prog_char
menuItem_9[] PROGMEM = "Show
date";
prog_char
menuItem_10[] PROGMEM = "Show
animations";
prog_char
menuItem_11[] PROGMEM = "tbd
11";
prog_char
menuItem_12[] PROGMEM = "Set
time";
prog_char
menuItem_13[] PROGMEM = "Set
year";
prog_char
menuItem_14[] PROGMEM = "Set
month";
prog_char
menuItem_15[] PROGMEM = "Set
day";
prog_char
menuItem_16[] PROGMEM = "tbd
16"; // ** remove if not used
prog_char
menuItem_17[] PROGMEM = "tbd
17";
prog_char
menuItem_18[] PROGMEM = "tbd
18";
prog_char
menuItem_19[] PROGMEM = "tbd
19";
prog_char
menuItem_20[] PROGMEM = "tbd
20";
prog_char
menuItem_21[] PROGMEM = "Turn &
push to set"; //status messages
from this point on
prog_char
menuItem_22[] PROGMEM = "*Updating
board";
prog_char
menuItem_23[] PROGMEM = "Asleep.
Wake at "; // include trailing
space
prog_char
menuItem_24[] PROGMEM =
"Chime";
prog_char
menuItem_25[] PROGMEM = ""; // these gaps are used because of
prog_char
menuItem_26[] PROGMEM =
"Temp"; // some sloppy code
in
prog_char
menuItem_27[] PROGMEM = ""; // getNextInformationalMessage()
prog_char
menuItem_28[] PROGMEM =
"Date";
prog_char
menuItem_29[] PROGMEM = "";
prog_char menuItem_30[]
PROGMEM = "Animations";
prog_char
menuItem_31[] PROGMEM = "";
prog_char
menuItem_32[] PROGMEM = "Sleep
at";
// 20 characters
maximum: 12345678901234567890
// Then set up a
table to refer to the strings:
PROGMEM const char
*menuTable[] = {
menuItem_0,
menuItem_1, menuItem_2, menuItem_3,
menuItem_4, menuItem_5, menuItem_6,
menuItem_7,
menuItem_8,
menuItem_9, menuItem_10, menuItem_11,
menuItem_12, menuItem_13, menuItem_14,
menuItem_15,
menuItem_16, menuItem_17, menuItem_18,
menuItem_19, menuItem_20, menuItem_21, menuItem_22, menuItem_23,
menuItem_24, menuItem_25, menuItem_26,
menuItem_27, menuItem_28, menuItem_29, menuItem_30, menuItem_31, menuItem_32 };
/***************************************************************
* Font taken from: *
*
http://www.openobject.org/opensourceurbanism/Bike_POV_Beta_4*
***************************************************************/
const byte
alphabet[][5] = { // each entry holds
codes for the five columns
{},{},{},{},{},{},{},{},
{},{},{},{},{},{},{},{},
{},{},{},{},{},{},{},{},
{},{},{},{},{},{},{},{},
{0x00,0x00,0x00,0x00,0x00}, // 0x20 32
{0x00,0x00,0x6f,0x00,0x00}, // ! 0x21 33
{0x00,0x07,0x00,0x07,0x00}, // " 0x22
34
{0x14,0x7f,0x14,0x7f,0x14}, // # 0x23 35
{0x00,0x07,0x04,0x1e,0x00}, // $ 0x24 36
{0x23,0x13,0x08,0x64,0x62}, // % 0x25 37
{0x36,0x49,0x56,0x20,0x50}, // & 0x26
38
{0x00,0x00,0x07,0x00,0x00}, // ' 0x27 39
{0x00,0x1c,0x22,0x41,0x00}, // ( 0x28 40
{0x00,0x41,0x22,0x1c,0x00}, // ) 0x29 41
{0x14,0x08,0x3e,0x08,0x14}, // * 0x2a 42
{0x08,0x08,0x3e,0x08,0x08}, // + 0x2b 43
{0x00,0x50,0x30,0x00,0x00}, // , 0x2c 44
{0x08,0x08,0x08,0x08,0x08}, // - 0x2d 45
{0x00,0x60,0x60,0x00,0x00}, // . 0x2e 46
{0x20,0x10,0x08,0x04,0x02}, // / 0x2f 47
{0x3e,0x51,0x49,0x45,0x3e}, // 0 0x30 48
{0x00,0x42,0x7f,0x40,0x00}, // 1 0x31 49
{0x42,0x61,0x51,0x49,0x46}, // 2 0x32 50
{0x21,0x41,0x45,0x4b,0x31}, // 3 0x33 51
{0x18,0x14,0x12,0x7f,0x10}, // 4 0x34 52
{0x27,0x45,0x45,0x45,0x39}, // 5 0x35 53
{0x3c,0x4a,0x49,0x49,0x30}, // 6 0x36 54
{0x01,0x71,0x09,0x05,0x03}, // 7 0x37 55
{0x36,0x49,0x49,0x49,0x36}, // 8 0x38 56
{0x06,0x49,0x49,0x29,0x1e}, // 9 0x39 57
{0x00,0x36,0x36,0x00,0x00}, // : 0x3a 58
{0x00,0x56,0x36,0x00,0x00}, // ; 0x3b 59
{0x08,0x14,0x22,0x41,0x00}, // < 0x3c 60
{0x14,0x14,0x14,0x14,0x14}, // = 0x3d 61
{0x00,0x41,0x22,0x14,0x08}, // > 0x3e 62
{0x02,0x01,0x51,0x09,0x06}, // ? 0x3f 63
{0x3e,0x41,0x5d,0x49,0x4e}, // @ 0x40 64
{0x7e,0x09,0x09,0x09,0x7e}, // A 0x41 65
{0x7f,0x49,0x49,0x49,0x36}, // B 0x42 66
{0x3e,0x41,0x41,0x41,0x22}, // C 0x43 67
{0x7f,0x41,0x41,0x41,0x3e}, // D 0x44 68
{0x7f,0x49,0x49,0x49,0x41}, // E 0x45 69
{0x7f,0x09,0x09,0x09,0x01}, // F 0x46 70
{0x3e,0x41,0x49,0x49,0x7a}, // G 0x47 71
{0x7f,0x08,0x08,0x08,0x7f}, // H 0x48 72
{0x00,0x41,0x7f,0x41,0x00}, // I 0x49 73
{0x20,0x40,0x41,0x3f,0x01}, // J 0x4a 74
{0x7f,0x08,0x14,0x22,0x41}, // K 0x4b 75
{0x7f,0x40,0x40,0x40,0x40}, // L 0x4c 76
{0x7f,0x02,0x0c,0x02,0x7f}, // M 0x4d 77
{0x7f,0x04,0x08,0x10,0x7f}, // N 0x4e 78
{0x3e,0x41,0x41,0x41,0x3e}, // O 0x4f 79
{0x7f,0x09,0x09,0x09,0x06}, // P 0x50 80
{0x3e,0x41,0x51,0x21,0x5e}, // Q 0x51 81
{0x7f,0x09,0x19,0x29,0x46}, // R 0x52 82
{0x46,0x49,0x49,0x49,0x31}, // S 0x53 83
{0x01,0x01,0x7f,0x01,0x01}, // T 0x54 84
{0x3f,0x40,0x40,0x40,0x3f}, // U 0x55 85
{0x0f,0x30,0x40,0x30,0x0f}, // V 0x56 86
{0x3f,0x40,0x30,0x40,0x3f}, // W 0x57 87
{0x63,0x14,0x08,0x14,0x63}, // X 0x58 88
{0x07,0x08,0x70,0x08,0x07}, // Y 0x59 89
{0x61,0x51,0x49,0x45,0x43}, // Z 0x5a 90
{0x00,0x14,0x00,0x14,0x00}, // [ 0x5b 91
Reworked as four dots for ticker display (was: {0x3c,0x4a,0x49,0x29,0x1e})
{0x02,0x04,0x08,0x10,0x20}, // \ 0x5c 92
{0x00,0x41,0x7f,0x00,0x00}, // ] 0x5d 93
{0x00,0x00,0x07,0x05,0x07}, // ^ 0x5e 94
Reworked as degree symbol for temperature display (was:
{0x04,0x02,0x01,0x02,0x04})
{0x40,0x40,0x40,0x40,0x40}, // _ 0x5f 95
{0x00,0x00,0x03,0x04,0x00}, // ` 0x60 96
{0x20,0x54,0x54,0x54,0x78}, // a 0x61 97
{0x7f,0x48,0x44,0x44,0x38}, // b 0x62 98
{0x38,0x44,0x44,0x44,0x20}, // c 0x63 99
{0x38,0x44,0x44,0x48,0x7f}, // d 0x64 100
{0x38,0x54,0x54,0x54,0x18}, // e 0x65 101
{0x08,0x7e,0x09,0x01,0x02}, // f 0x66 102
{0x0c,0x52,0x52,0x52,0x3e}, // g 0x67 103
{0x7f,0x08,0x04,0x04,0x78}, // h 0x68 104
{0x00,0x44,0x7d,0x40,0x00}, // i 0x69 105
{0x20,0x40,0x44,0x3d,0x00}, // j 0x6a 106
{0x00,0x7f,0x10,0x28,0x44}, // k 0x6b 107
{0x00,0x41,0x7f,0x40,0x00}, // l 0x6c 108
{0x7c,0x04,0x18,0x04,0x78}, // m 0x6d 109
{0x7c,0x08,0x04,0x04,0x78}, // n 0x6e 110
{0x38,0x44,0x44,0x44,0x38}, // o 0x6f 111
{0x7c,0x14,0x14,0x14,0x08}, // p 0x70 112
{0x08,0x14,0x14,0x18,0x7c}, // q 0x71 113
{0x7c,0x08,0x04,0x04,0x08}, // r 0x72 114
{0x48,0x54,0x54,0x54,0x20}, // s 0x73 115
{0x04,0x3f,0x44,0x40,0x20}, // t 0x74 116
{0x3c,0x40,0x40,0x20,0x7c}, // u 0x75 117
{0x1c,0x20,0x40,0x20,0x1c}, // v 0x76 118
{0x3c,0x40,0x30,0x40,0x3c}, // w 0x77 119
{0x44,0x28,0x10,0x28,0x44}, // x 0x78 120
{0x0c,0x50,0x50,0x50,0x3c}, // y 0x79 121
{0x44,0x64,0x54,0x4c,0x44}, // z 0x7a 122
{0x00,0x08,0x36,0x41,0x41}, // { 0x7b 123
{0x00,0x00,0x7f,0x00,0x00}, // | 0x7c 124
{0x41,0x41,0x36,0x08,0x00}, // } 0x7d 125
{0x04,0x02,0x04,0x08,0x04}, // ~ 0x7e 126
};
// Variables for bit
pattern array
byte bitPattern[25]; // 25 columns of 8 bits (ignore last bit)
byte pixelList[35]; // array used for random reordering
// Setup shift
register variables
#define number_of_74hc595s 12 // How many shift
registers
#define numOfRegisterPins
number_of_74hc595s * 8 // do not touch
#define rowSelectorPinStart
numOfRegisterPins - 16 // what shift
register number does row selector circuit begin? (Last 16 pins of the shift
register circuit)
//Variables for shift register
int SER_Pin = 6; // pin 14 on the 75HC595 Shift
Register (Data) ** switch to byte
int RCLK_Pin = 7; // pin 12 on the 75HC595 Shift
Register (Latch)
int SRCLK_Pin = 8; // pin 11 on the 75HC595 Shift
Register (Clock)
boolean registers[numOfRegisterPins]; // bit array for the total number of 595
registers
// Variables for
rotary encoder & push button
int encoderPin1 = 2; // these pins can not be changed 2/3 are
special pins ** switch to byte
int encoderPin2 = 3; // used for interrupts on rotary encoder
int encoderSwitchPin = 4; // push button switch on
rotary encoder
volatile int lastEncoded = 0; // for interrupt routine - do
not change
volatile int encoderValue = 1; // for interrupt routine - do
not change
int lastEncoderValue = 1;
const int encoderGap = 2; // size of gap from
encoder before we pay attention to it
const byte encoderDelay = 200; // delay in reading encoder
value
// Constants for
pushbuttons
const byte timePlusButton = 13;
const byte timeMinusButton = 12;
const byte clockSleepButton = 10;
const byte chimeSleepButton = 11;
// Variables for
menuing system
const byte maxMenuItems = 15; // Maximum number of menu
items (starts at zero)
const byte msgUseDial = 21; // "Turn
dial"
char buffer[20]; // max size of message, and max
size of lcd screen
byte activeMenuSelection = 0; // Which menu item is active
byte selectedMenuItem = 99;
boolean chimeSwitch; // switches and other things to remember
boolean quarterChime;
boolean halfChime;
byte sleepHour;
byte wakeHour;
byte chimeSleepHour;
byte chimeWakeHour;
boolean showTemp;
boolean showDate;
boolean showAnimations;
boolean showPhrases;
boolean hourlyChimeStyle;
byte prevSpecialDisplay = 0; // which special display
went last time?
byte prevInfoMessage = 0; // which informational
display message went last time?
// Constants for
EEPROM memory (refers to index location in EEPROM where we will store the
value) using EEPROM.write(address, value) and EEPROM.read(address, value)
const byte chimeSwitch_EEPROM = 0;
const byte quarterChime_EEPROM = 1;
const byte halfChime_EEPROM = 3;
const byte sleepHour_EEPROM = 4;
const byte wakeHour_EEPROM = 5;
const byte chimeSleepHour_EEPROM = 6;
const byte chimeWakeHour_EEPROM = 7;
const byte showTemp_EEPROM = 8;
const byte showDate_EEPROM = 9;
const byte showAnimations_EEPROM = 10;
const byte showPhrases_EEPROM = 11;
const byte hourlyChimeStyle_EEPROM = 12;
// Variables for
Real Time Clock RTC
byte prevSeconds = 0;
byte prevMinutes = 0;
boolean checkForChime = FALSE;
boolean checkForSpecialDisplay = FALSE;
boolean sleepMode = FALSE;
boolean priorSleepMode = FALSE;
const int specialDisplayInterval = 967; // consider a special display every 967
seconds (16 minutes, 7 seconds)
int secondsToNextSpecialDisplay =
specialDisplayInterval;
boolean nextFlippingStep = false; // is it time to animate the clock ticker?
byte secondsPassed = 0; // used in calculations with
nextFlippingStep
byte animationStep = 0; // which animation step of
the ticker is active
boolean steadyTickerFlag = false; // flag set when a regular colon is shown as
the ticker
boolean topOfTheHour = false; // flag to show whether it's the top of
the hour
byte flipTickerStyle = 0; // ticker style from 0 thru 4
byte flipTickerDelaySpeed = 0;
byte animationTime = 99; // what time of the hour will the
random animation occur? Avoid it from happening on startup with 99 value
// Variables for
playing pong
int xOffset = 0;
int yOffset = 0;
// Variables for
servo motor
int servoPin = 5;
Servo servo;
int angle = 0; // servo position in degrees
// Variables for
flipping
const int pauseTime = 5; // was 25, 5 - prev 50
const int solenoidTime = 110; // was 110 -
prev 150
void setup()
{
lcd.init(); //
initialize the lcd
lcd.backlight(); // turn on the backlight
// Initialize shift
registers
pinMode(SER_Pin, OUTPUT); // Shift Register pin
pinMode(RCLK_Pin, OUTPUT); // Shift Register pin
pinMode(SRCLK_Pin, OUTPUT); // Shift Register pin
clearRegisters(); // reset all register pins
writeRegisters();
// Initialize servo
motor
servo.attach(servoPin); // initialize
servo
// Configure
pushbutton pins
pinMode(timePlusButton, INPUT);
pinMode(timeMinusButton, INPUT);
pinMode(clockSleepButton, INPUT);
pinMode(chimeSleepButton, INPUT);
digitalWrite(timePlusButton, HIGH);
digitalWrite(timeMinusButton, HIGH);
digitalWrite(clockSleepButton, HIGH);
digitalWrite(chimeSleepButton, HIGH);
// Configure rotary
encoder
pinMode(encoderPin1, INPUT); // rotary encoder
pinMode(encoderPin2, INPUT); // interrupt pins
pinMode(encoderSwitchPin, INPUT); // push button
digitalWrite(encoderPin1, HIGH); // turn pullup resistor on
digitalWrite(encoderPin2, HIGH); // turn pullup resistor on
digitalWrite(encoderSwitchPin, HIGH); // turn pullup resistor on
attachInterrupt(0, updateEncoder, CHANGE); // call updateEncoder() when any high/low
changed seen
attachInterrupt(1, updateEncoder, CHANGE); // on interrupt 0 (pin 2), or interrupt 1 (pin
3)
// Configure RTC
//RTC.setHours(16); // At startup: set time - TEST CODE
ONLY
//RTC.setMinutes(14); // Note: Resetting
time can help fix RTC clock errors
//RTC.setSeconds(50);
//RTC.setYear(2014);
//RTC.setMonth(9);
//RTC.setDate(13);
//RTC.setClock(); // Only needed when
initiating a new clock
// Clear out
bitPattern array
for (byte i = 0 ; i < 25; i++){
//bitPattern[i] = B11111111; // presume all dots are yellow **make
function
bitPattern[i] = B00000000; // presume all dots are black **remove for
production code
}
//clearBoard(' '); // ** add for production code
// Refresh variables
from EEPROM
chimeSwitch = EEPROM.read( chimeSwitch_EEPROM );
quarterChime = EEPROM.read( quarterChime_EEPROM );
halfChime = EEPROM.read(
halfChime_EEPROM );
sleepHour = EEPROM.read(
sleepHour_EEPROM );
wakeHour = EEPROM.read(
wakeHour_EEPROM );
chimeSleepHour = EEPROM.read( chimeSleepHour_EEPROM );
chimeWakeHour = EEPROM.read( chimeWakeHour_EEPROM );
showTemp = EEPROM.read(
showTemp_EEPROM );
showDate = EEPROM.read(
showDate_EEPROM );
showAnimations = EEPROM.read( showAnimations_EEPROM );
showPhrases = EEPROM.read( showPhrases_EEPROM );
hourlyChimeStyle = EEPROM.read( hourlyChimeStyle_EEPROM );
}
void loop() {
// main clock code
follows
if
(abs(lastEncoderValue - encoderValue) > encoderGap) { // menu
selection has changed
if (encoderValue > lastEncoderValue) { // figure out what the new menu
selection is
activeMenuSelection ++; // add 1 to menu
if (activeMenuSelection >
maxMenuItems) activeMenuSelection = 0;
}else{
activeMenuSelection --; //
subtract 1 from menu
if (activeMenuSelection > 200)
activeMenuSelection = maxMenuItems;
}
printLCDActiveMenu(activeMenuSelection);
lastEncoderValue = encoderValue;
}
if(!digitalRead(encoderSwitchPin))
{ // check if encoder button has
been pushed - note the not operation
delay(500); //
pause to allow time for button to be released *** make constant
selectedMenuItem = activeMenuSelection;
menuSystem(selectedMenuItem); // process menu selection
}
RTC.readClock(); //
find out what the time is
topOfTheHour =
false;
if
((RTC.getMinutes() == 0) && (RTC.getSeconds() == 0)) { // if it's the top of the hour
topOfTheHour = true; // set a flag
flipTickerStyle = random(0,5); // for the
next hour, select a ticker style from 0 thru 4
flipTickerDelaySpeed = random(1,7); // for the next
hour, select a ticker speed from every 1 second to every 6
// determine when this hour's animation
will occur (if it's enabled)
animationTime = (random(0,4) * 15) + random(3,14); //
quarter hour segment + 3 to 13 minutes (avoids chiming times)
}
// is it top of the
hour and time for sleep mode?
if ((RTC.getHours()
== sleepHour) && (topOfTheHour == true) || sleepMode != false) { // if it's exactly sleep hour at the top of
the hour, or sleep mode
sleepMode = true; // it is now sleep mode
if (priorSleepMode == false) { // this is the first time we're sleeping, so
set the display
printStatusMessage(23); // "Asleep. Wake at"
lcd.print(convertTwelveHour(wakeHour)); // print wake time
clearBoard(' '); // blank out entire board
priorSleepMode = true;
}
}
// is it wake time?
if ((RTC.getHours()
== wakeHour) && (RTC.getMinutes() == 0) && (RTC.getSeconds() ==
0) || sleepMode == false) {
if (priorSleepMode == true) { // we were in sleep mode and just came out of
it
prevMinutes = 99; // force clock update during
showTime
clearLine(1);
}
sleepMode = false;
priorSleepMode = false;
}
showTime(); // update LCD and board, if required
if ((showAnimations
!= false) && (animationTime == RTC.getMinutes()) &&
(RTC.getSeconds() == 15)) paintAnimation(); //
do random animation if enabled and if it's time
if (checkForChime !=
false)
chimeLogic(RTC.getHours(), RTC.getMinutes()); // chime logic
if
(checkForSpecialDisplay == true)
specialDisplay(); // is it time for a
special display?
checkForChime =
false; // ensure
we don't check next time round
checkForSpecialDisplay
= false; // ensure we
don't check next time round
checkButtons(); // see if
any pushbuttons are being pressed
delay(100); // slow things down, it's only a clock
//=====================
end of clock code
}
void flipBY(byte
column, byte row) {
setNegativeColumnOn(column);
setPositiveRowOn(row);
writeRegisters();
delay(solenoidTime);
setPositiveRowOff(row);
setNegativeColumnOff(column);
writeRegisters();
setBitPattern(column, row, 1); // update array with a record of this change
}
void flipYB(byte
column, byte row) {
setPositiveColumnOn(column);
setNegativeRowOn(row);
writeRegisters();
delay(solenoidTime);
setNegativeRowOff(row);
setPositiveColumnOff(column);
writeRegisters();
setBitPattern(column, row, 0); // update array with a record of this change
}
void
setNegativeColumnOn(byte column) {
// To turn negative column on:
// Enable = hi, In1 (pin 2 or 10) = high,
In2 (pin 7 or 15) = low
setRegisterPin((column * 3) - 2, 1); // In1: High
setRegisterPin((column * 3) - 1, 0); // In2: Low
setRegisterPin((column * 3) - 3, 1); // Enable
}
void
setNegativeColumnOff(byte column) {
// To turn negative column off:
// Enable = low, In1 (pin 2 or 10) = low,
In2 (pin 7 or 15) = low
setRegisterPin((column * 3) - 2, 0); // In1: Low
setRegisterPin((column * 3) - 1, 0); // In2: Low
setRegisterPin((column * 3) - 3, 0); // Disable
}
void
setPositiveColumnOn(byte column) {
// To turn negative column on:
// Enable = hi, In1 (pin 2 or 10) = low,
In2 (pin 7 or 15) = high
setRegisterPin((column * 3) - 2, 0); // In1: Low
setRegisterPin((column * 3) - 1, 1); // In2: High
setRegisterPin((column * 3) - 3, 1); // Enable
}
void
setPositiveColumnOff(byte column) {
// To turn negative column off:
// Enable = low, In1 (pin 2 or 10) = low,
In2 (pin 7 or 15) = low
setRegisterPin((column * 3) - 2, 0); // In1: Low
setRegisterPin((column * 3) - 1, 0); // In2: Low
setRegisterPin((column * 3) - 3, 0); // Disable
}
void setNegativeRowOff(byte
row) {
// position in array is row-1... set it to
0
setRegisterPin(rowSelectorPinStart + row -
1, 0);
}
void
setNegativeRowOn(byte row) {
// position in array is row-1... set it to
1
setRegisterPin(rowSelectorPinStart + row -
1, 1);
}
void
setPositiveRowOff(byte row) {
// position in array is row+6... set it to
0
setRegisterPin(rowSelectorPinStart + row +
6, 0);
}
void
setPositiveRowOn(byte row) {
// position in array is row+6... set it to
1
setRegisterPin(rowSelectorPinStart + row +
6, 1);
}
void
clearRegisters() { //set all register
pins to LOW
for(int i = numOfRegisterPins - 1; i
>= 0; i--){
registers[i] = LOW;
}
}
void
writeRegisters() { // Set and display
registers - 595's
digitalWrite(RCLK_Pin, LOW);
for(int i = numOfRegisterPins - 1; i
>= 0; i--){
digitalWrite(SRCLK_Pin, LOW);
int val = registers[i];
digitalWrite(SER_Pin, val);
digitalWrite(SRCLK_Pin, HIGH);
}
digitalWrite(RCLK_Pin, HIGH);
}
void
setRegisterPin(int index, int value) {
//set an individual pin HIGH or LOW
registers[index] = value;
}
void
renderBoard(char* boardText){
byte renderStyle = random(0,10); // pick a rendering style
between 0 and 9
for (byte unit = 1; unit < 6; unit++) { // for each unit on the board 1 thru
5
char currentCharacter =
boardText[unit-1]; // gets the ASCII value of the current
letter.
if (unit == 5) { // special treatment for last digit
switch (renderStyle) {
case 0:
renderCharacterSequential(unit,
currentCharacter); //
now print that character
break;
case 1:
renderCharacterSequential(unit,
currentCharacter);
break;
case 2:
fadeTransition(unit,
currentCharacter);
break;
case 3:
fadeTransition(unit,
' ');
fadeTransition(unit,
currentCharacter);
break;
case 4:
barCollapse(unit);
renderCharacterSequential(unit,
currentCharacter);
break;
default: // in case of values 5 thru 9 (i.e, about
half the time)
fadeTransition(unit,
currentCharacter);
break;
}
}else{
renderCharacterSequential(unit,
currentCharacter);
}
} // do remaining units
}
byte
calculateStartColumn(byte unit) { //
utility function to return column number
return ((unit-1) * 5) + 1;
}
void
renderCharacterSequential(byte unit, char currentCharacter) { // flip dots one-by-one, column at a
time
// unit is the physical character display
on the board (1-5)
byte startColumn =
calculateStartColumn(unit); // map unit (1-6) to the column
on the board (1-25)
for (byte column = 1; column < 6;
column++){
byte alphabetColumn =
alphabet[currentCharacter][column-1]; // retrieves the column from the 2D array
font
for (byte row = 1; row < 8;
row++){ // for row 1 thru 7
byte bitValue =
!!(alphabetColumn & (1 << (row-1))); //
retrieve the bit from the 2d array font
// The double-not (!!) forces 0
to be 0 and non-zero to be 1
byte activeDisplayBit =
!!(bitPattern[(startColumn + column - 1)-1] & (1 << (row-1))); // retrieve the bit from the bitPattern array
byte needToFlip = bitValue ^
activeDisplayBit; // XOR: do we need to flip this bit on the
board?
if (needToFlip == 1) {
if (activeDisplayBit == 1)
{ // need to flip from yellow to black
flipYB((startColumn +
column - 1), row);
//setBitPattern((startColumn
+ column - 1), row, 0); // update
array with a record of this change
} else { //
need to flip from black to yellow
flipBY((startColumn +
column - 1), row);
//setBitPattern((startColumn
+ column - 1), row, 1); // update
array with a record of this change
}
delay(pauseTime);
}
}
}
}
void
barCollapse(byte unit) { // collapse
character top-down
byte startColumn =
calculateStartColumn(unit); // map unit (1-6) to the
column on the board (1-25)
byte lowestActiveRow = 0;
for (byte column = 1; column <= 5;
column++) { // for each column
lowestActiveRow =
findLowestActiveRow((startColumn + column - 1)); // find flipped dot with
lowest row number
if (lowestActiveRow > 0) { // if there are indeed any flipped dots in
this column
for (byte row = (lowestActiveRow
+ 1); row <= 7; row++) { // for each row from this to end
flipBY((startColumn +
column - 1), row ); // flip it black
//setBitPattern((startColumn
+ column - 1), row, 1); // update array
with a record of this change 1 = Y
} // next row
}
} // next column
for (byte i = 1; i++; i <= 7) { // for
up to 7 cycles, start counting down the
rows
for (byte column = 1; column <= 5;
column++) { // for each column
lowestActiveRow =
findLowestActiveRow((startColumn + column - 1)); // find lowest row with active
dot
if (lowestActiveRow > 0) {
flipYB((startColumn +
column - 1), lowestActiveRow ); // flip
it black
//setBitPattern((startColumn
+ column - 1), lowestActiveRow, 0); //
update array with a record of this change
}
} // next column
}
}
void
fadeTransition(byte unit, char characterIn) { // fade to characterIn
byte startColumn =
calculateStartColumn(unit); // map unit (1-6) to the
column on the board (1-25)
randomizePixels(); // randomize list of 5x7
= 35 35 pixels
for (byte i = 0; i < 35; i++) { // loop through the array, which is now
randomized
byte column = ((pixelList[i]-1) / 7)
+ 1 ;
// look at a pixel in this column
byte row = pixelList[i] - ((column -
1) * 7); // look at this row
byte alphabetColumn = alphabet[characterIn][column-1]; // retrieves the column from the 2D
array font
byte bitValue = !!(alphabetColumn
& (1 << (row-1))); //
retrieve the bit from the 2d array font
byte activeDisplayBit =
!!(bitPattern[(startColumn + column - 1)-1] & (1 << (row-1))); // retrieve the bit from the bitPattern array
byte needToFlip = bitValue ^
activeDisplayBit; // XOR:
do we need to flip this bit on the board?
if (needToFlip == 1) {
if (activeDisplayBit == 1)
{ // need to flip from yellow to black
flipYB((startColumn +
column - 1), row);
//setBitPattern((startColumn
+ column - 1), row, 0); // update
array with a record of this change
} else { // need to flip from black to
yellow
flipBY((startColumn +
column - 1), row);
//setBitPattern((startColumn
+ column - 1), row, 1); // update
array with a record of this change
}
delay(pauseTime);
}
}
}
byte
findLowestActiveRow(byte column) { // find flipped dot with lowest row number -
columns 1 thru 25
byte lowestFlippedRow = 0; // setup
returning variable
for (byte row = 7; row >= 1; row--) { //
starting at row 7 and counting down
byte activeDisplayBit =
!!(bitPattern[column - 1] & (1 << (row-1))); // retrieve the bit from the bitPattern array
if (activeDisplayBit != 0)
lowestFlippedRow = row;// if it is yellow (aka not black) then set the lowest
flipped row
}
return
lowestFlippedRow ;
}
void
setBitPattern(byte column, byte row, boolean status) { // update
array with a record of a dot's value
if (status == FALSE) {
bitClear(bitPattern[column-1],
(row-1)); // dot is black
} else {
bitSet(bitPattern[column-1],
(row-1)); // dot is yellow
}
}
void
randomizePixels() { // randomize list of 5x7 = 35 35 pixels (needs ** randomize
based on analog port)
for (byte i = 0; i <= 34; i++) { //
first, populate the array in sequence
pixelList[i] = i;
}
for (byte i = 0; i <= 34; i++) { // now
resort it
byte r = random(i,35); // check
visipack code
byte temp = pixelList[i];
pixelList[i] = pixelList[r];
pixelList[r] = temp;
}
}
void menuSystem(byte
selectedMenuItem) {
switch
(selectedMenuItem) { //
case 0: // chime on & off
printStatusMessage(msgUseDial);
printLCDOptionStatus(chimeSwitch);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue !=
lastEncoderValue) { //
figure out what the new menu selection is
chimeSwitch = ~chimeSwitch;
printLCDOptionStatus(chimeSwitch);
}
}
lastEncoderValue = encoderValue;
delay(encoderDelay);
} while (digitalRead(encoderSwitchPin)); // exit when the button
is pressed to set time
EEPROM.write( chimeSwitch_EEPROM,
chimeSwitch ); // update
EEPROM
delay(500); //** make constant
break;
case 1: // quarter chime on & off
printStatusMessage(msgUseDial);
printLCDOptionStatus(quarterChime);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue !=
lastEncoderValue) { //
figure out what the new menu selection is
quarterChime =
~quarterChime;
printLCDOptionStatus(quarterChime);
}
}
lastEncoderValue = encoderValue;
delay(encoderDelay);
} while (digitalRead(encoderSwitchPin)); // exit when the button
is pressed to set time
EEPROM.write( quarterChime_EEPROM,
quarterChime ); // update
EEPROM
delay(500); //** make constant
break;
case 2: // half chime on & off
printStatusMessage(msgUseDial);
printLCDOptionStatus(halfChime);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue !=
lastEncoderValue) { //
figure out what the new menu selection is
halfChime = ~halfChime;
printLCDOptionStatus(halfChime);
}
}
lastEncoderValue = encoderValue;
delay(encoderDelay);
} while (digitalRead(encoderSwitchPin)); // exit when the button
is pressed to set time
EEPROM.write( halfChime_EEPROM, halfChime
); // update EEPROM
delay(500); //** make constant
break;
case 3: // hourly chime style (single or multiple
chimes)
printStatusMessage(msgUseDial);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue !=
lastEncoderValue) { //
figure out what the new menu selection is
hourlyChimeStyle =
~hourlyChimeStyle; // false (0):
single, !false (255): multi
lcd.setCursor(8,3); // print user-friendly on/off message
lcd.print(">
");
if (hourlyChimeStyle ==
FALSE) {
lcd.print("Single");
}else{
lcd.print("Multi
"); //
trailing space to overwrite "Single"
}
}
}
lastEncoderValue = encoderValue;
delay(encoderDelay);
} while (digitalRead(encoderSwitchPin)); // exit when the button
is pressed to set time
EEPROM.write( hourlyChimeStyle_EEPROM,
hourlyChimeStyle ); // update EEPROM
delay(500); //** make constant
break;
case 4: // set clock
sleep time
printStatusMessage(msgUseDial);
printLCDOptionHour(sleepHour);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue >
lastEncoderValue) { //
figure out what the new menu selection is
sleepHour =
addHour(sleepHour);
}else{
sleepHour =
subtractHour(sleepHour);
}
printLCDOptionHour(sleepHour);
}
lastEncoderValue = encoderValue;
delay(encoderDelay);
} while
(digitalRead(encoderSwitchPin)); //
exit when the button is pressed to set time
EEPROM.write( sleepHour_EEPROM, sleepHour
); // update EEPROM
delay(500); //
pause to allow time for button to be released
break;
case 5: // set clock
wake time
printStatusMessage(msgUseDial);
printLCDOptionHour(wakeHour);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue >
lastEncoderValue) { //
figure out what the new menu selection is
wakeHour =
addHour(wakeHour);
}else{
wakeHour =
subtractHour(wakeHour);
}
printLCDOptionHour(wakeHour);
}
lastEncoderValue = encoderValue;
delay(encoderDelay);
} while
(digitalRead(encoderSwitchPin)); //
exit when the button is pressed to set time
EEPROM.write( wakeHour_EEPROM, wakeHour ); // update EEPROM
delay(500); //
pause to allow time for button to be released
break;
case 6: // set chime
sleep time
printStatusMessage(msgUseDial);
printLCDOptionHour(chimeSleepHour);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap){
if (encoderValue >
lastEncoderValue) { //
figure out what the new menu selection is
chimeSleepHour =
addHour(chimeSleepHour);
}else{
chimeSleepHour =
subtractHour(chimeSleepHour);
}
printLCDOptionHour(chimeSleepHour);
}
lastEncoderValue = encoderValue;
delay(encoderDelay);
} while
(digitalRead(encoderSwitchPin)); //
exit when the button is pressed to set time
EEPROM.write( chimeSleepHour_EEPROM,
chimeSleepHour ); // update EEPROM
delay(500); //
pause to allow time for button to be released
break;
case 7: // set chime
wake time
printStatusMessage(msgUseDial);
printLCDOptionHour(chimeWakeHour);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue >
lastEncoderValue) { //
figure out what the new menu selection is
chimeWakeHour =
addHour(chimeWakeHour);
}else{
chimeWakeHour =
subtractHour(chimeWakeHour);
}
printLCDOptionHour(chimeWakeHour);
}
lastEncoderValue = encoderValue;
delay(encoderDelay);
} while
(digitalRead(encoderSwitchPin)); //
exit when the button is pressed to set time
EEPROM.write( chimeWakeHour_EEPROM,
chimeWakeHour ); // update
EEPROM
delay(500); //
pause to allow time for button to be released
break;
case 8: // show temperature on & off
printStatusMessage(msgUseDial);
printLCDOptionStatus(showTemp);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue !=
lastEncoderValue) { //
figure out what the new menu selection is
showTemp = ~showTemp;
printLCDOptionStatus(showTemp);
}
}
lastEncoderValue = encoderValue;
delay(encoderDelay);
} while (digitalRead(encoderSwitchPin)); // exit when the button
is pressed to set time
EEPROM.write( showTemp_EEPROM, showTemp ); // update EEPROM
delay(500); //** make constant
break;
case 9: // show date on & off
printStatusMessage(msgUseDial);
printLCDOptionStatus(showDate);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue !=
lastEncoderValue) { //
figure out what the new menu selection is
showDate = ~showDate;
printLCDOptionStatus(showDate);
}
}
lastEncoderValue = encoderValue;
delay(encoderDelay);
} while (digitalRead(encoderSwitchPin)); // exit when the button
is pressed to set time
EEPROM.write( showDate_EEPROM, showDate ); // update EEPROM
delay(500); //** make constant
break;
case 10: // show special animations on & off
printStatusMessage(msgUseDial);
printLCDOptionStatus(showAnimations);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue !=
lastEncoderValue) { //
figure out what the new menu selection is
showAnimations =
~showAnimations;
printLCDOptionStatus(showAnimations);
}
}
lastEncoderValue = encoderValue;
delay(encoderDelay);
} while (digitalRead(encoderSwitchPin)); // exit when the button
is pressed to set time
EEPROM.write( showAnimations_EEPROM,
showAnimations ); // update
EEPROM
delay(500); //** make constant
break;
case 11: // show phrases on & off
printStatusMessage(msgUseDial);
printLCDOptionStatus(showPhrases);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue !=
lastEncoderValue) { //
figure out what the new menu selection is
showPhrases = ~showPhrases;
printLCDOptionStatus(showPhrases);
}
}
lastEncoderValue = encoderValue;
delay(encoderDelay);
} while (digitalRead(encoderSwitchPin)); // exit when the button
is pressed to set time
EEPROM.write( showPhrases_EEPROM,
showPhrases ); // update
EEPROM
delay(500); //** make constant
break;
case 12: //set time
printStatusMessage(msgUseDial);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue >
lastEncoderValue) { //
figure out what the new menu selection is
addMinute();
}else{
subtractMinute();
}
}
lastEncoderValue = encoderValue;
delay(encoderDelay); // ** make constant
} while
(digitalRead(encoderSwitchPin)); //
exit when the button is pressed to set time
delay(500); // pause to allow time for button to be released
break;
case 13: //set year
printStatusMessage(msgUseDial);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue > lastEncoderValue)
{ // figure out what the
new menu selection is
addYear();
}else{
subtractYear();
}
}
lastEncoderValue = encoderValue;
delay(encoderDelay); // ** make constant
} while
(digitalRead(encoderSwitchPin)); //
exit when the button is pressed to set time
delay(500); // pause to allow time
for button to be released
break;
case 14: //set month
printStatusMessage(msgUseDial);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue >
lastEncoderValue) { //
figure out what the new menu selection is
addMonth();
}else{
subtractMonth();
}
}
lastEncoderValue = encoderValue;
delay(encoderDelay); // ** make constant
} while
(digitalRead(encoderSwitchPin)); //
exit when the button is pressed to set time
delay(500); // pause to allow time
for button to be released
break;
case 15: //set day
printStatusMessage(msgUseDial);
do{
if (abs(lastEncoderValue -
encoderValue) > encoderGap) {
if (encoderValue >
lastEncoderValue) { //
figure out what the new menu selection is
addDay();
}else{
subtractDay();
}
}
lastEncoderValue = encoderValue;
delay(encoderDelay); // ** make constant
} while
(digitalRead(encoderSwitchPin)); //
exit when the button is pressed to set time
delay(500); // pause to allow time
for button to be released
break;
}
clearLine(3);
clearLine(2);
clearLine(1);
}
void
printLCDOptionStatus(boolean option) { //
print user-friendly on/off message
lcd.setCursor(8,3);
lcd.print("> O");
if (option == FALSE) {
lcd.print("FF");
}else{
lcd.print("N ");
}
}
void
printLCDInformationalStatus(boolean option) { //
print user-friendly on/off message
lcd.setCursor(8,1);
lcd.print("> O");
if (option == FALSE) {
lcd.print("FF");
}else{
lcd.print("N ");
}
}
void
printLCDOptionHour(byte hour) { // print
user-friendly hour
lcd.setCursor(8,3);
printLCDDigits(hour);
lcd.print(" Hrs");
}
void checkButtons()
{
byte buttonDelay = 255;
while (digitalRead(timePlusButton) == LOW)
{ // add time
addMinute();
delay(buttonDelay);
buttonDelay--;
}
while (digitalRead(timeMinusButton) == LOW)
{ // subtract time
subtractMinute();
delay(buttonDelay);
buttonDelay--;
}
if
(digitalRead(chimeSleepButton) == LOW) { // flip value of chimeswitch
chimeSwitch = ~chimeSwitch;
EEPROM.write( chimeSwitch_EEPROM,
chimeSwitch ); // update
EEPROM
printLCDOptionStatus(chimeSwitch);
lcd.setCursor(0,2);
lcd.print("Chime");
delay(2000);
clearLine(2);
clearLine(3);
}
if
(digitalRead(clockSleepButton) == LOW) { // flip value of sleep
if (sleepMode == true) {
sleepMode = false;
} else {
sleepMode = true;
}
printLCDOptionStatus(sleepMode);
lcd.setCursor(0,2);
lcd.print("Sleep");
delay(2000);
clearLine(2);
clearLine(3);
}
}
void chimeLogic(byte
hour, byte minute){ // chime logic
if ((chimeSwitch !=
FALSE) && // chime is on
(isItSleepMode(chimeSleepHour,
chimeWakeHour, hour) == FALSE) && //
and it's not chime sleep window
(sleepMode == FALSE) && // we're not sleeping
(isItSleepMode(sleepHour, wakeHour, RTC.getHours())
== FALSE)) { // and it's not clock
sleep window
if ((minute == 15) && (quarterChime
!= FALSE)) { // do quarter chime
chime(1);
}
if ((minute == 30) && ((halfChime
!= FALSE) || (quarterChime != FALSE))) {
//
do half past chime
chime(1);
delay(650);
chime(2);
}
if ((minute == 45) && (quarterChime
!= FALSE)) { // do three quarters chime
chime(1);
delay(1000);
chime(2);
delay(1000);
chime(1);
}
if (minute == 0) {
if (hourlyChimeStyle != FALSE) { // do full set of chimes
chime(1);
delay(1000);
chime(2);
delay(300);
chime(2);
delay(300);
chime(1);
delay(3000);
for (byte i = 0; i <
convertTwelveHour(hour); i++) { // adjusted for 12 hours
chime(3);
delay(2000);
}
} else { // only one chime
chime(3);
}
}
}
}
void chime(byte
hammer) { // move hammer into
place, bang once
switch (hammer) {
case 1:
servo.write(88); // lower tone at 90 degrees
break;
case 2:
servo.write(75); // mid tone at 90 degrees
break;
case 3:
servo.write(62); // high tone at 90 degrees
break;
}
delay(300); //
wait for servo to get into position
bangHammer();
}
void bangHammer() {
setRegisterPin(75, 1); // engage solenoid
writeRegisters(); //
delay(20); //
wait was 15
setRegisterPin(75, 0); // turn off solenoid
writeRegisters(); //
}
void
specialDisplay() {// is it time for a special display?
if (isItSleepMode(sleepHour, wakeHour,
RTC.getHours()) == FALSE) { // it's not
sleep mode, so...
byte i = getNextSpecialDisplay(); // figure out which special display, if any,
goes next
switch (i) {
case 1:
displayTemperature();
break;
case 2:
displayDate();
break;
}
prevMinutes = 99; // force dots to update
}
}
byte
getNextSpecialDisplay() { // figure out
which special display, if any, goes next
prevSpecialDisplay++; // point
to the next special display in sequence
if (prevSpecialDisplay > 2)
prevSpecialDisplay = 1; // reset to the
beginning if necessary
if ((prevSpecialDisplay == 1) &&
(showTemp == FALSE)) prevSpecialDisplay++;
if ((prevSpecialDisplay == 2) &&
(showDate == FALSE)) prevSpecialDisplay++; // ** bugs around here... ? check
return prevSpecialDisplay;
}
byte
getNextInformationalMessage() { //
figure out which informational message goes next
prevInfoMessage++; // point to the next special
display in sequence
if (prevInfoMessage > 10) prevInfoMessage
= 1; // reset to the beginning if
necessary
return prevInfoMessage;
}
void
displayTemperature() { // show
temperature for 5 seconds, if switch is on
printStatusMessage(22); // display message "Updating
board"
clearLine(0);
showTemperature();
clearLine(1); // remove "Updating board"
message
delay(10000); // make constant **
clearLine(0); // remove "Temp" message
}
void displayDate() { // show date for 5 seconds, if switch is on
printStatusMessage(22); // display message "Updating
board"
clearLine(0);
showFullDate();
clearLine(1); // remove "Updating board" message
delay(10000); // make constant **
clearLine(0);
}
byte addHour(byte
hour) {
if (hour >= 23) {
hour = 0;
} else {
hour++;
}
return hour;
}
byte
subtractHour(byte hour) {
if (hour == 0) {
hour = 23;
} else {
hour--;
}
return hour;
}
boolean
isItSleepMode(byte sleepHour, byte wakeHour, byte nowHour){ // is it currently a sleep mode (for chime or
for clock?)
boolean sleep = FALSE;
if (sleepHour > wakeHour){
sleep = TRUE;
if ((nowHour < sleepHour)
&& (nowHour >= wakeHour)) sleep = FALSE;
}
if (sleepHour < wakeHour){
sleep = FALSE;
if ((nowHour > sleepHour)
&& (nowHour < wakeHour)) sleep = TRUE;
}
return sleep; // false = it's not sleep hour, true = it is sleep hour
}
byte
convertTwelveHour(byte hour) { // convert
incoming 24 hour and return its 12 hour equivalent
if (hour < 13) return hour;
if (hour > 12) return (hour-12);
}
boolean dialHasTurned(){ // Has the dial been turned on the rotary
encoder? set flag if dial was turned or switch set to on
boolean dialChange = false;
if ((abs(lastEncoderValue - encoderValue)
> encoderGap)) // dial has been
turned OR
dialChange = true; // then set to true so we can break and
change dial/go to off/go to sleep
return dialChange;
}
void
updateEncoder(){ // interrupt routine
called whenever rotary dial is moved
int MSB = digitalRead(encoderPin1); //MSB =
most significant bit
int LSB = digitalRead(encoderPin2); //LSB =
least significant bit
int encoded = (MSB << 1) |LSB;
//converting the 2 pin value to single number
int sum
= (lastEncoded << 2) | encoded; //adding it to the previous
encoded value
if(sum == 0b1101 || sum == 0b0100 || sum ==
0b0010 || sum == 0b1011) encoderValue ++;
if(sum == 0b1110 || sum == 0b0111 || sum ==
0b0001 || sum == 0b1000) encoderValue --;
lastEncoded = encoded; //store this value for
next time
if (encoderValue < 1) encoderValue = 99;
//prevent negative numbers on menu
if (encoderValue > 99) encoderValue = 1;
//prevent negative numbers on menu
}
void showTime() {
if (RTC.getSeconds()
!= prevSeconds) { // if one second has
passed...
showTimeOnLCD();
if (isItSleepMode(sleepHour, wakeHour,
RTC.getHours()) == FALSE && sleepMode == false)
flipTicker(flipTickerStyle,flipTickerDelaySpeed); // animate the ticker, styles 0 thru 4
if ((RTC.getMinutes() != prevMinutes)
&& isItSleepMode(sleepHour, wakeHour, RTC.getHours()) == FALSE
&& sleepMode == false) { // if
one minute has passed and not sleep mode
displayFullTime(); // update flip dot board
}
if (prevMinutes != RTC.getMinutes()) checkForChime = true; //
consider whether to chime when we return
secondsToNextSpecialDisplay --;
if ((secondsToNextSpecialDisplay < 1)
&& isItSleepMode(sleepHour, wakeHour, RTC.getHours()) == FALSE) {
checkForSpecialDisplay = true; // consider whether to do a
special display when we return
secondsToNextSpecialDisplay =
specialDisplayInterval; // reset counter
}
}
prevSeconds =
RTC.getSeconds(); // save for
next time round
prevMinutes =
RTC.getMinutes();
}
void showTimeOnLCD()
{
lcd.setCursor(0, 0);
lcd.print("Time");
printLCDDigits(convertTwelveHour(RTC.getHours()));
// show 12 hour clock
printLCDDigits(RTC.getMinutes());
printLCDDigits(RTC.getSeconds());
lcd.print(" ");
if (RTC.getHours() > 11) {
lcd.print("P");
}else{
lcd.print("A");
}
lcd.print("M "); // trailing space solves a problem where a
digit sometimes gets printed to the right of the time - clear it out instead
// print an information message...
if ((RTC.getSeconds() % 5 == 0) &&
(sleepMode == false)) { // we only
want to do this every 5 seconds and not in sleep mode
byte message =
getNextInformationalMessage();
// only do this if message is 1,3,5,7
(i.e. every 5 seconds)
if (message % 2) { // message number is odd
printStatusMessage(23 +
message); // message starts at position
24
lcd.print(" ");
switch (message) { // now print value
case 1:
printLCDInformationalStatus(chimeSwitch);
break;
case 3:
printLCDInformationalStatus(showTemp);
break;
case 5:
printLCDInformationalStatus(showDate);
break;
case 7:
printLCDInformationalStatus(showAnimations);
break;
case 9: // status of next sleep event
//lcd.setCursor(8,1);
lcd.print(convertTwelveHour(sleepHour));
}
} else {
clearLine(1);
}
}
}
void
displayFullTime(){
printStatusMessage(22); // display message "Updating
board"
byte mins = RTC.getMinutes(); // get minutes
byte hours =
convertTwelveHour(RTC.getHours()); // get
hours and convert to 12 hour format
String xtime; // this is the string we'll build
if (hours < 10) xtime += ' '; // pad hours with a space if reqd
xtime += String(hours); // add hours
xtime += ' '; // add space for ticker
if (mins < 10) xtime += '0'; // pad minutes with a zero if reqd
xtime += String(mins); // add minutes
char boardText[6] = ""; // prep the char array
xtime.toCharArray(boardText,6); // copy the string to the char array
//strncpy(boardText, xtime, 6);
renderBoard(boardText); // render the board
clearLine(1); // remove "Updating board" message
}
void flipTicker(byte
style, byte secondsGap) { // call once a
second, have logic determine what to do
// figure out how many seconds have passed
since the last time around
nextFlippingStep = false;
secondsPassed++;
if (secondsPassed >= secondsGap) {
nextFlippingStep = true; // signal that it's time to do the next
animation of the ticker style
animationStep++; // which step of the animation
we're ready to do *** need to reset to 0 when changing style
secondsPassed = 0;
}
if ((style == 0) &&
(steadyTickerFlag == false)) { // two
large dots on permanently
renderCharacterSequential(3,':'); // render a ":"
steadyTickerFlag = true; // don't want to
update board every second, so set a flag to show it's already been rendered
}
if (nextFlippingStep == true) { // time to do the
next animation of the ticker style
steadyTickerFlag = false; // will force a
board unit refresh when style = 0
switch (style) {
case 1: // two dots at the same time flipping every X seconds
switch (animationStep) {
case 1: // animation step 1: display dots
flipBY(13,3);
flipBY(13,5);
break;
case 2: // animation step 2:
hide dots
flipYB(13,3);
flipYB(13,5);
animationStep = 0; // reset counter for next time round
break;
}
break;
case 2: // two dots one at a time flipping every X seconds
switch (animationStep) {
case 1: // animation step 1: display dot 1
flipBY(13,3);
break;
case 2: // animation step 2: hide dot 1
flipYB(13,3);
break;
case 3: // animation step 3: display dot 2
flipBY(13,5);
break;
case 4: // animation step 4: hide dot 2
flipYB(13,5);
animationStep = 0; // reset counter for next time round
break;
}
break;
case 3: // four dots one at a time flipping every X seconds
switch (animationStep) {
case 1: // animation step 1: four dots
renderCharacterSequential(3,91); // [ 0x5b 91 Reworked as four dots
break;
case 2: // animation step 2: black out one dot
flipYB(12,3);
break;
case 3: // animation step 3: four dots
renderCharacterSequential(3,91);
break;
case 4: // animation step 4: black out one dot
flipYB(14,3);
break;
case 5: // animation step 5: four dots
renderCharacterSequential(3,91);
break;
case 6: // animation step 6: black out one dot
flipYB(14,5);
break;
case 7: // animation step 7: four dots
renderCharacterSequential(3,91);
break;
case 8: // animation step 8: black out one dot
flipYB(12,5);
animationStep = 0; // reset counter for next time round
break;
}
break;
case 4: // one dot flipping every X seconds
switch (animationStep) {
case 1: // animation step 1: display dot
flipBY(13,4);
break;
case 2: // animation step 2: hide dot
flipYB(13,4);
animationStep = 0; // reset counter for next time round
break;
}
break;
}
}
}
void clearBoard(char
fillCharacter) { // clean out the entire
board with fillCharacter
for (byte i = 0 ; i < 25; i++){
bitPattern[i] = B11111111; // presume all dots are yellow
}
for (byte i = 1; i < 6; i++) {
renderCharacterSequential(i, ' ');
renderCharacterSequential(i,
fillCharacter);
}
}
void paintSinWave()
{
//clearBoard(' '); // clear out the board
for (byte wave = 0; wave <= 4; wave++) { // do X number of waves
float sineFrequency = random(23,50);
sineFrequency = sineFrequency / 100; // convert to a number between
2.3 and 4.9
for (byte column = 1; column <= 25;
column++) { // for each column...
//Serial.print(column);
//Serial.print("
");
//Serial.print(calculateSinPosition(column,
sineFrequency)); // paint new value
flipBY(column,
calculateSinPosition(column, sineFrequency));
//setBitPattern(column,
calculateSinPosition(column, sineFrequency), 1); // update array with a record of this change
}
//Serial.println(" ");
for (byte column = 1; column <=
25; column++) { // for each column...
flipYB(column,
calculateSinPosition(column, sineFrequency));
//setBitPattern(column,
calculateSinPosition(column, sineFrequency), 0); // update array with a record of this change
}
}
}
int
calculateSinPosition(byte loopCounter, float sineFrequency) {
float transformedSinWave = 0;
float sinWave = 0; // prepare a floating
point number
sinWave = sin(loopCounter * sineFrequency); // this is the value, in radians,
for this point of the sine wave
transformedSinWave = ((sinWave + 1) * 4);
byte index = transformedSinWave; // ram floating
point number into integer... it will be close enough for union work
if (index == 0) index++; // prevent
zero from being returned
return index;
}
void
paintAnimation() { // select an animation
byte animation = random(0,4); // pick a number 0 thru 3
for (byte i = 1; i < 6; i++) { // first, clear the board
fadeTransition(i, ' ');
}
switch (animation) {
case 0:
paintExpandingLine();
break;
case 1:
paintCheckerBoard();
break;
case 2:
paintHorizontalLines();
break;
case 3:
paintSinWave();
break;
}
prevMinutes = 99; //
force a clock redraw
}
void
paintExpandingLine() {
byte row = random(2,7);
for (byte column = 1; column < 24;
column++) { // extend column
flipBY(column, row);
}
delay(4000);
for (byte column = 23; column > 0;
column--) { // retract column
flipBY(column-1, row-1);
flipBY(column-1, row+1);
flipYB(column, row);
flipYB(column, row-1);
flipYB(column, row+1);
}
}
void paintCheckerBoard()
{
for (byte row = 1; row < 8; row=row+2) {
for (byte column = 1; column < 25;
column=column+2) { //
flipBY(column,
row);
flipBY(column+1, row+1);
}
}
delay(4000);
for (byte row = 1; row < 8; row=row+2) {
for (byte column = 1; column < 25;
column=column+2) { //
flipYB(column,
row);
flipYB(column+1, row+1);
}
}
}
void
paintHorizontalLines() {
byte column[4];
byte row[4];
for (byte i = 0; i < 4; i++) { // pick 4 random points on the board and place
in an array
column[i] = random(1,21);
row[i] = random(1,8);
flipBY(column[i],row[i]); // flip each of the 4 points BY
}
delay(4000); // wait 4 seconds
for (byte j = 0; j < 4; j++) { // for each point,
for (byte i = 1; i < 6; i++) { // extend the line out by 5 additional lines
(6 total)
flipBY(column[j]+i, row[j]);
}
}
delay(4000);
for (byte j = 0; j < 4; j++) { // for each point,
for (byte i = 0; i < 5; i++) { // remove all but the last point
flipYB(column[j]+i, row[j]);
}
}
// for all remaining points, scroll them to
column 13
boolean stillMoving = true;
do {
stillMoving = true;
for (byte j = 0; j < 4; j++) { // for each point,
if ((column[j]+5) < 13) {
column[j] = column[j] + 1;
flipBY(column[j]+5, row[j]);
flipYB(column[j]+4,
row[j]);
}
if ((column[j]+5) > 13) {
column[j] = column[j] - 1;
flipBY(column[j]+5,
row[j]);
flipYB(column[j]+6,
row[j]);
}
}
// are any dots not at column 13?
for (byte j = 0; j < 4; j++) { // for each point,
if ((column[j]+5) != 13)
stillMoving = false;
}
} while (stillMoving == false);
// then collapse row 30
barCollapse(3);
delay(4000);
}
void playPong() { // not currently implemented
int angle = 90; // right direction
to begin with
calculateNewOffsets(angle);
int xAxis = 13;
int yAxis = 4;
for (int i = 0; i < 50; i++){ // going to do 350
drawings
flipBY(xAxis, yAxis); // draw the ball
//matrix.drawPixel(xAxis, yAxis,
BLACK); // black out the ball
next time we do a show()
//if (angle > 180) drawPadle( 0,
yAxis); // if ball coming to
the left, move left padle
//if (angle < 180) drawPadle(11,
yAxis); // if ball coming to
the right, move right padle
if (xAxis > 23 || xAxis < 1 ||
yAxis > 6 || yAxis < 2) { // have
we hit a wall?
angle = calculateNewAngle(angle,
xAxis, yAxis);
calculateNewOffsets(angle);
}
delay(60);
flipYB(xAxis, yAxis);
xAxis = xAxis + xOffset;
yAxis = yAxis + yOffset;
}
}
int
calculateNewAngle(int angle, int xAxis, int yAxis) {
if (angle ==
0) {angle = 135;}
else if (angle == 45 && xAxis > 23) {angle = 270;}
else if (angle == 45 && yAxis < 2) {angle = 135;}
else if (angle == 90) {angle
= 315;}
else if (angle == 135 && xAxis >
6) {angle = 225;}
else if (angle == 135 && yAxis >
6) {angle = 45;}
else if (angle == 180) {angle = 45;}
else if (angle == 225 && xAxis <
2) {angle = 135;}
else if (angle == 225 && yAxis >
6) {angle = 315;}
else if (angle == 270) {angle = 90;}
else if (angle == 315 && xAxis <
2) {angle = 45;}
else if (angle == 315 && yAxis <
2) {angle = 225;}
return angle;
}
void
calculateNewOffsets(int angle){
switch (angle) { // what are new x and
y offsets?
case
0:
xOffset = 0;
yOffset = -1;
break;
case 45:
xOffset = 1;
yOffset =
random(-1,1); // to
prevent endless back-and-forth
break;
case 90:
xOffset = 1;
yOffset = random(-1,2); //
was 0;
break;
case 135:
xOffset = 1;
yOffset = 1;
break;
case 180:
xOffset = 0;
yOffset = 1;
break;
case 225:
xOffset = -1;
yOffset = +1;
break;
case 270:
xOffset = -1;
yOffset = 0;
break;
case 315:
xOffset = -1;
yOffset = -1;
break;
}
}
void
showTemperature(){ // see
http://playground.arduino.cc/Learning/OneWire
byte i;
byte present = 0;
byte data[12];
byte addr[8];
int HighByte, LowByte, TReading, Tc_100,
Whole;
if ( !ds.search(addr)) {
//Serial.print("No more
addresses.\n");
ds.reset_search();
delay(250);
return;
}
//Serial.print("R=");
//for( i = 0; i < 8; i++) {
//Serial.print(addr[i], HEX);
//Serial.print(" ");
//}
//if ( OneWire::crc8( addr, 7) != addr[7]) {
//
Serial.print("CRC is not valid!\n");
//
return;
//}
//if ( addr[0] != 0x28) { // Address 0x28 = DS18B20 sensor
//
//Serial.print(addr[0]);
//
Serial.print(": Device is not a DS18S20 family device.\n");
// return;
// }
// The DallasTemperature library can do all
this work for you!
ds.reset();
ds.select(addr);
ds.write(0x44,1); // start conversion, with parasite
power on at the end
delay(1000); // maybe 750ms is enough, maybe not
// we might do a ds.depower() here, but the
reset will take care of it.
present = ds.reset();
ds.select(addr);
ds.write(0xBE); // Read Scratchpad
//Serial.print("P=");
//Serial.print(present,HEX);
//Serial.print(" ");
for ( i = 0; i < 9; i++) { // we need 9 bytes
data[i] = ds.read();
//Serial.print(data[i], HEX);
//Serial.print(" ");
}
//Serial.print(" CRC=");
//Serial.print( OneWire::crc8( data, 8),
HEX);
//Serial.println();
LowByte = data[0];
HighByte = data[1];
TReading = (HighByte << 8) + LowByte;
Tc_100 = (6 * TReading) + TReading / 4; // multiply by (100 * 0.0625) or 6.25
Whole = (((Tc_100 / 100) * 9 ) / 5 ) +
32; // separate off the whole and
fractional portions and convert from C to F
// show temperature on LCD
lcd.setCursor(0, 0);
lcd.print("Temp");
printLCDDigits(Whole);
lcd.print("F");
String xtime; // ** rename
xtime += String(Whole);
xtime += '^';
xtime += 'F';
char boardText[6] = ""; // prep the char array
xtime.toCharArray(boardText,6); // copy the string to the char array
renderBoard(boardText); // render the board
}
void showFullDate()
{
lcd.setCursor(0, 0); // show date on LCD
lcd.print("Date");
printLCDDigits(RTC.getMonth());
lcd.print("/");
lcd.print(RTC.getDate());
String xtime; // ** rename
xtime += String(RTC.getMonth());
xtime += '/';
xtime += String(RTC.getDate());
char boardText[6] = ""; // prep the char array
xtime.toCharArray(boardText,6); // copy the string to the char array
renderBoard(boardText); // render the board
}
void
printLCDDigits(int digits) { // utility
function for lcd display: prints preceding colon and leading 0
lcd.print(":");
if(digits < 10) lcd.print('0');
lcd.print(digits);
}
void addMinute() {
if (RTC.getMinutes() == 59){
RTC.setMinutes(0);
if (RTC.getHours() == 23){
RTC.setHours(0);
}else{
RTC.setHours(RTC.getHours()+1);}
}else{
RTC.setMinutes(RTC.getMinutes()+1);}
RTC.setClock();
showTimeOnLCD();
}
void
subtractMinute() {
if (RTC.getMinutes() == 0){
RTC.setMinutes(59);
if (RTC.getHours() == 0){
RTC.setHours(23);
}else{
RTC.setHours(RTC.getHours()-1);}
}else{
RTC.setMinutes(RTC.getMinutes()-1);}
RTC.setClock();
showTimeOnLCD();
}
void addYear() {
RTC.setYear(RTC.getYear()+1);
RTC.setClock();
displayFullTime();
}
void subtractYear()
{
RTC.setYear(RTC.getYear()-1);
RTC.setClock();
displayFullTime();
}
void addMonth() {
if (RTC.getMonth() == 12){
RTC.setMonth(1);
}else{
RTC.setMonth(RTC.getMonth()+1);
}
RTC.setClock();
displayFullTime();
}
void subtractMonth()
{
if (RTC.getMonth() == 1){
RTC.setMonth(12);
}else{
RTC.setMonth(RTC.getMonth()-1);
}
RTC.setClock();
displayFullTime();
}
void addDay() {
if (RTC.getDate() == 31){
RTC.setDate(1);
}else{
RTC.setDate(RTC.getDate()+1);
}
RTC.setClock();
displayFullTime();
}
void subtractDay() {
if (RTC.getDate() == 1){
RTC.setDate(31);
}else{
RTC.setDate(RTC.getDate()-1);
}
RTC.setClock();
displayFullTime();
}
void
printLCDActiveMenu(byte menu) { // display
the currently-selected menu item
clearLine(2);
getMessage(menu);
lcd.setCursor(0, 2);
lcd.print(">");
lcd.print(buffer);
}
void getMessage(byte
index){ //retrieve eeprom message and put it into buffer
strcpy_P(buffer,
(char*)pgm_read_word(&(menuTable[index]))); // Necessary casts and dereferencing,
just copy.
}
void
printStatusMessage(byte i){
//print status message #i on lcd
getMessage(i);
clearLine(1);
lcd.setCursor(0, 1);
//lcd.print("*");
lcd.print(buffer);
}
void clearLine(byte
i){ //clear a specific line
for (byte j = 0; j <20; j++){
lcd.setCursor(j,i);
lcd.print(" ");
}
}