/*
alphaclock_18.pde
Software for the Five Letter Word Clock desiged by
Evil Mad Scientist Laboratories http://www.evilmadscientist.com
Target: ATmega644A, clock at 16 MHz.
Designed to work with Arduino 23; untested with other versions.
Also (1) requires the "old" DateTime library: http://www.arduino.cc/playground/Code/DateTime
Also (2) requires Sanguino extensions to Arduino, rev 0023, available from:
http://code.google.com/p/sanguino/downloads/list
* May require "boards.txt" file to be edited as well, for atmega644 (not -P).
* May require "pins_arduino.c" and "pins_arduino.h" to be edited as well, for 644.
- Arduino does not directly support the '644A, so the outgoing serial port may not be usable from
within the Arduino IDE.
* Bootloader should be set to give device signature as ATmega644P.
Untested with newer versions of Arduino.
Version 1.0 - 12/17/2011
Copyright (c) 2011 Windell H. Oskay. All right reserved.
http://www.evilmadscientist.com/
Modified (2012) by odometer
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this library. If not, see .
*/
#include // For saving settings
#include // For optional RTC module
// #include // For optional Serial Sync
// #include // For optional Serial Sync
#define AllLEDsOff(); PORTA |= 127;
// The buttons are located at PB0, PB1, PB2, PB3
#define buttonmask 15
// Default "idle" time to wait before saving settings in EEPROM
#define IdleDelay 5000000UL
// adjustment for clock rate
// "theoretical" value is 500000UL (five hundred thousand)
// decreasing this number speeds up the clock
// 1 unit = about 1.2 sec/week
const unsigned long ClockRateAdj = 500000UL;
// calendar constants
const unsigned int MinValidDays = 1401; // 2000-01-01
const unsigned int MaxValidDays = 37925; // 2099-12-31
const unsigned int DaysLoop = (MaxValidDays - MinValidDays) + 1;
const unsigned int DaysOffset1970 = 9556; // days from 1970-01-01 to 1996-03-01
const byte MaxBright = 10; // maximum brightness
byte PINBLast;
byte TimeError;
byte ShowDateNow;
boolean countHalfSec;
boolean timeIsRaw;
byte HalfSecNow;
byte SpinnerNow;
byte TwelveHrNow;
byte BCDSecNow;
byte MinNow; // BCD
byte HrNow; // BCD, wall time
byte StdHrNow; // BCD, the "real" hour (no DST)
byte WeekdayNow; // Mon=1 ... Sun=7
byte DateNow; // BCD
byte MonthNow; // BCD, human month numbering
byte YearNow; // two digits BCD, implied century is 2000-2099
unsigned int DaysNow; // day 0 = 1996-03-01
unsigned long GregNow; // 0x0WDDMMYY
byte WkNumNow; // BCD
unsigned int StdDaysNow;
boolean DSTNow; // are we now on Daylight Saving Time?
unsigned int OldDays;
unsigned long OldGreg;
byte OldWkNum;
unsigned int OldDSTDayCheck;
byte OldDSTDayResult;
unsigned long TimeFromPC;
//byte ZeroHourBlankNow;
byte IdleTimeEnded;
// char AMPM24HdisplayNow; // Units character
byte ZeroHourBlankAlrm;
char AMPM24HdisplayAlrm;
byte CharacterBuffer[10]; // What position in char array
byte bufl1[10]; // First byte (low power)
byte bufh1[10]; // second byte (high power)
byte bufh2[10]; // third byte (high power)
char stringTemp[5];
/* Start at ASCII 32 (decimal), character ' ' (space).
Byte format: A, B, C, order sent to shift register.
A: First byte, "low power"
B: Second byte, "high power," MSB
C: Third byte, "high power," LSB
*/
char WeekArray[] = {' ',' ', 'M','O', 'T','U', 'W','E',
'T','H', 'F','R', 'S','A', 'S','U'};
byte AlphaArray[] = {
0,0,0, // [space] ASCII 32
0,0,3, // ! (Yes, lame, but it's the "standard." Better Suggestions welcome.)
0,0,40, // "
60,1,44, //#
63,1,42, //$
57,3,106, //%
14,3,98, //&
0,0,64, // '
0,0,192, // (
0,2,16, // )
48,3,240, // *
48,1,32, //+
0,2,0, // ,
48,0,0, // -
64,0,0, // . (lower DP)
0,2,64, // /
15,0,15, // 0
// for slashed zero use 15,2,79,
0,0,67, // 1
47,2,1, // 2
47,0,66, // 3
48,0,11, // 4
63,0,10, // 5
63,0,14, // 6
51,2,64, // crossed 7
63,0,15, // 8
63,0,11, // 9
10,1,47, // single-cell figure 10 (month number, etc.)
0,0,15, // single-cell figure 11
42,1,13, // single-cell figure 12
//0,1,32, //: // making room for single-cell 10 through 12
//0,2,32, //;
//0,0,192, //<
60,0,0, //=
0,2,16, //>
34,1,1, //?
47,0,13, //@
51,0,15, //A
47,1,35, //B
15,0,12, //C
15,1,35, //D
63,0,12, //E
51,0,12, //F
47,0,14, //G
48,0,15, //H
15,1,32, //I
12,0,7, //J
16,0,204, //K
12,0,12, //L
0,0,95, //M
0,0,159, //N
15,0,15, //O
51,0,13, //P
15,0,143, //Q
51,0,141, //R
15,0,144, //S
3,1,32, //T
12,0,15, //U
0,2,76, //V
0,2,143, //W
0,2,208, //X
0,1,80, //Y
15,2,64, //Z
//10,1,32, // [ // commented out: it's not like we need these
//0,0,144, // backslash
//5,1,32, // ]
//0,2,128, // ^
//12,0,0, // _
//0,0,16, // `
0,0,32, // spinner position 0
0,0,64,
32,0,0,
0,0,128,
0,1,0,
0,2,0,
16,0,0,
0,0,16 // spinner position 7
};
// Starting offset of our ASCII array:
#define asciiOffset 32
// Starting offset for number zero
#define numberOffset 16
// Starting offset for spinner
#define spinnerOffset 59
#define singleCellTen 26
//Faster latch macro!
#define Latch(); PORTC |= 4;PORTC &= 251;
void AlphaWrite (byte l1, byte h1, byte h2)
{ // Transmit data to to LED drivers through SPI
PORTA |= 127; // Blank all character "rows" (A0-A5) and LED driver
SPDR = l1;
while (!(SPSR & _BV(SPIF))) {
} //Wait for transmission complete
SPDR = h1;
while (!(SPSR & _BV(SPIF))) {
} //Wait for transmission complete
SPDR = h2;
while (!(SPSR & _BV(SPIF))) {
} //Wait for transmission complete
}
byte OptionNameSequence; // State variable for multi-word option labels
byte SoundSequence;
unsigned long WordStopTime;
byte DisplayWordMode;
byte SerialDisplayMode;
void LoadCharBuffer (char WordIn[])
{
CharacterBuffer[0] = (WordIn[4] - asciiOffset);
CharacterBuffer[1] = (WordIn[3] - asciiOffset);
CharacterBuffer[2] = (WordIn[2] - asciiOffset);
CharacterBuffer[3] = (WordIn[1] - asciiOffset);
CharacterBuffer[4] = (WordIn[0] - asciiOffset);
}
void DisplayWordSequence (char WordIn[], unsigned long durationMicros)
{
WordStopTime = micros() + durationMicros;
CharacterBuffer[0] = (WordIn[4] - asciiOffset);
CharacterBuffer[1] = (WordIn[3] - asciiOffset);
CharacterBuffer[2] = (WordIn[2] - asciiOffset);
CharacterBuffer[3] = (WordIn[1] - asciiOffset);
CharacterBuffer[4] = (WordIn[0] - asciiOffset);
LoadShiftRegBuffers();
DisplayWordMode = 1;
OptionNameSequence++;
}
#define EELength 8
byte EEvalues[EELength];
byte MainBright;
byte FadeMode; // Not used at present.
byte HourMode;
byte AlarmEnabled; // If the "ALARM" function is currently turned on or off.
byte AlarmTimeHr;
byte AlarmTimeMin;
byte NightLightType; //0
byte AlarmTone;
// "Factory" default configuration can be configured here:
#define MainBrightDefault 3 // no need to make it too bright
#define FadeModeDefault 1
#define HourModeDefault 0 // I like the 24-hour clock
#define AlarmEnabledDefault 0
#define AlarmTimeHrDefault 0x09
#define AlarmTimeMinDefault 0x00
#define NightLightTypeDefault 0
#define AlarmToneDefault 1
unsigned long LastTime;
//byte TimeSinceButton;
//byte LastAlarmCheckMin;
byte ExtRTC;
void updateNightLight()
{
if (NightLightType == 0)
analogWrite(14, 0);
if (NightLightType == 1)
analogWrite(14, 60);
if (NightLightType == 2)
analogWrite(14, 255);
}
#define TIME_MSG_LEN 13 // time sync to PC is HEADER followed by unix time_t as ten ascii digits (Was 11)
#define TIME_HEADER 255 // Header tag for serial time sync message
boolean getPCtime() {
char charTemp, charTemp2;
int i;
TimeFromPC = 0;
// if time sync available from serial port, update time and return true
while(Serial.available() >= TIME_MSG_LEN ){ // time message consists of a header and ten ascii digits
/*if (Serial.read() != TIME_HEADER)
{
while((Serial.peek() != TIME_HEADER) && (Serial.peek() >= 0)) // Flush buffer up until next 0xFF.
Serial.read();
DisplayWordSequence("FLUSH",500); //TODO: Remove this debug message
}
*/
if( Serial.read() == TIME_HEADER) {
// DisplayWordSequence("RECV ",100); //TODO: Remove this debug message
// Read command, next two bytes:
charTemp = Serial.read();
charTemp2 = Serial.read();
if( charTemp == 'S' ){
// DisplayWordSequence("RECVS",100); //TODO: Remove this debug message
if( charTemp2 == 'T' ){
// Time setting mode:
//DisplayWordSequence("RECVT",100); //TODO: Remove this debug message
// time_t pctime = 0;
for( i=0; i < 10; i++){
char c= Serial.read();
if( c >= '0' && c <= '9'){
TimeFromPC = (10 * TimeFromPC) + (c - '0') ; // convert digits to a number
}
}
// DateTime.sync(pctime); // Sync Arduino clock to the time received on the serial port
// no, we don't need to do it that way
return true; // return true if time message received on the serial port
}
}
else if( charTemp == 'A' ){
if( charTemp2 == '0' ) {
// DisplayWordSequence("RECA0",500); //TODO: Remove this debug message
// ASCII display mode, first 5 chars will be displayed.
for( i=0; i < 10; i++){
charTemp = Serial.read();
if (i < 5)
{
CharacterBuffer[4 - i] = charTemp - asciiOffset;
}
else
{
CharacterBuffer[i] = charTemp;
}
}
LoadShiftRegBuffers();
DisplayWordMode = 1;
for( i=5; i < 10; i++){
if (CharacterBuffer[i] == 'L')
bufl1[9 - i] |= 64; // Add lower DP
if (CharacterBuffer[i] == 'U')
bufl1[9 - i] |= 128; // Add upper DP
if (CharacterBuffer[i] == 'B')
bufl1[9 - i] |= 192; // Add both DPs
}
SerialDisplayMode = 1;
// Serial.println("Writing Text!");
}
}
else if( charTemp == 'M' ){
if( charTemp2 == 'T' )
{
// Clock display mode
SerialDisplayMode = 0;
// Serial.println("Resuming clock display!");
for( i=0; i < 10; i++){ // Read dummy input....
charTemp = Serial.read();
}
}
}
}
else
{
DisplayWordSequence("ERROR",200000UL); //Display error!
}
}
return false; //if no message return false
}
void printDigits(byte digits){
// utility function for digital clock display: prints preceding colon and leading 0
Serial.print(":");
if(digits < 10)
Serial.print('0');
Serial.print(digits,DEC);
}
/*
void digitalClockDisplay(){
// digital clock display of current date and time
Serial.print(DateTime.Hour,DEC);
printDigits(DateTime.Minute);
printDigits(DateTime.Second);
Serial.print(" ");
Serial.print(DateTimeStrings.dayStr(DateTime.DayofWeek));
Serial.print(" ");
Serial.print(DateTimeStrings.monthStr(DateTime.Month));
Serial.print(" ");
Serial.println(DateTime.Day,DEC);
}
*/
// Modes:
byte VCRmode; // In VCR mode, the clock blinks at you because the time hasn't been set yet.
//byte FactoryResetDisable; // To make sure that we don't accidentally reset the settings...
byte SettingTime;
byte SettingAlarm;
byte AlarmNow; // Alarm is actually going off, right now.
byte HoldTimeSet;
byte HoldOption;
byte HoldAlarmSet;
byte HoldLoopCount;
byte MomentaryOverridePlus;
byte MomentaryOverrideMinus;
unsigned long microsCopy;
unsigned long NextAdvance;
unsigned long NextAdvanceSound;
unsigned long endIdleTime;
unsigned long NumAdvances;
void ApplyDefaults (void) {
// VARIABLES THAT HAVE EEPROM STORAGE AND DEFAULTS...
// FadeMode = FadeModeDefault;
MainBright = MainBrightDefault;
HourMode = HourModeDefault;
AlarmEnabled = AlarmEnabledDefault;
AlarmTimeHr = AlarmTimeHrDefault;
AlarmTimeMin = AlarmTimeMinDefault;
AlarmTone = AlarmToneDefault;
NightLightType = NightLightTypeDefault;
}
void EEReadSettings (void) {
byte detectBad = 0;
byte value = 255;
value = EEPROM.read(0);
if ((value >= 1) && (value <= MaxBright))
MainBright = value;
else {
MainBright = MainBrightDefault; // Turn back on when power goes back on-- don't leave it dark.
EESaveSettings();
}
value = EEPROM.read(1);
if (value > 2)
detectBad = 1;
else
HourMode = value;
value = EEPROM.read(2);
if (value > 1)
detectBad = 1;
else
AlarmEnabled = value;
value = EEPROM.read(3);
if ((value > 0x23) || bcdBad(value))
detectBad = 1;
else
AlarmTimeHr = value;
value = EEPROM.read(4);
if ((value > 0x59) || bcdBad(value))
detectBad = 1;
else
AlarmTimeMin = value;
value = EEPROM.read(5);
if (value > 4)
detectBad = 1;
else
AlarmTone = value;
value = EEPROM.read(6);
if (value > 4)
detectBad = 1;
else
NightLightType = value;
/*
if (detectBad){
ApplyDefaults();
EESaveSettings();
}
*/
}
void EESaveSettings (void){
//EEPROM.write(Addr, Value);
byte detectBad = 0;
byte EEPROMwritten = 0;
byte value = 255;
// Careful if you use this function: EEPROM has a limited number of write
// cycles in its life. Good for human-operated buttons, bad for automation.
if (MainBright > MaxBright)
detectBad = 1;
else
{
value = EEPROM.read(0);
// if (MainBright != value){
if (MainBrightDefault != value) { // let's not rewrite this every time we change the brightness
// EEPROM.write(0, MainBright);
EEPROM.write(0, MainBrightDefault);
EEPROMwritten = 1;
}
}
if (HourMode > 2)
detectBad = 1;
else
{
value = EEPROM.read(1);
if (HourMode != value){
EEPROM.write(1, HourMode);
EEPROMwritten = 1;
}
}
if (AlarmEnabled > 1)
detectBad = 1;
else
{
value = EEPROM.read(2);
if (AlarmEnabled != value){
EEPROM.write(2, AlarmEnabled);
EEPROMwritten = 1;
}
}
if ((AlarmTimeHr > 0x23) || ((AlarmTimeHr & 15) > 9))
detectBad = 1;
else
{
value = EEPROM.read(3);
if (AlarmTimeHr != value){
EEPROM.write(3, AlarmTimeHr);
EEPROMwritten = 1;
}
}
if ((AlarmTimeMin > 0x59) || bcdBad(AlarmTimeMin))
detectBad = 1;
else
{
value = EEPROM.read(4);
if (AlarmTimeMin != value){
EEPROM.write(4, AlarmTimeMin);
EEPROMwritten = 1;
}
}
if (AlarmTone > 4)
detectBad = 1;
else
{
value = EEPROM.read(5);
if (AlarmTone != value){
EEPROM.write(5, AlarmTone);
EEPROMwritten = 1;
}
}
if (NightLightType > 4)
detectBad = 1;
else
{
value = EEPROM.read(6);
if (NightLightType != value){
EEPROM.write(6, NightLightType);
EEPROMwritten = 1;
}
}
// Optional: Blink LEDs off to indicate when we're writing to the EEPROM
/*
if (EEPROMwritten)
{
AllLEDsOff();
delay(100);
}
*/
}
void RTCsetTime(int daysIn, byte hourIn, byte minuteIn, byte secondIn)
{
Wire.beginTransmission(104); // 104 is DS3231 device address
Wire.send(0); // start at register 0
Wire.send(secondIn); //Send seconds
Wire.send(minuteIn); //Send minutes
Wire.send(hourIn); //Send hours
unsigned long greg = days2greg(daysIn);
Wire.send((byte)((greg>>24)&255)); //Send weekday
Wire.send((byte)((greg>>16)&255)); //Send date
Wire.send((byte)((greg>>8)&255)); //Send month
Wire.send((byte)(greg&255)); //Send year
Wire.endTransmission();
}
byte RTCgetTime()
{ // Read out time from RTC module, if present
// send request to receive data starting at register 0
byte status = 0;
Wire.beginTransmission(104); // 104 is DS3231 device address
Wire.send(0); // start at register 0
Wire.endTransmission();
Wire.requestFrom(104, 7); // request seven bytes (time and date)
byte seconds, minutes, hours, weekday, date, month, year;
unsigned int days;
unsigned long greg;
byte errorfound = 0;
// unsigned int temptime1, temptime2;
byte updatetime = 0;
while(Wire.available())
{
status = 1;
seconds = Wire.receive();
minutes = Wire.receive();
hours = Wire.receive();
weekday = Wire.receive(); greg = weekday;
date = Wire.receive(); greg = (greg << 8) + date;
month = Wire.receive(); greg = (greg << 8) + month;
year = Wire.receive(); greg = (greg << 8) + year;
if (greg == OldGreg) {
days = OldDays;
}
else {
days = greg2days(greg);
}
if (days>MaxValidDays) errorfound = 1;
}
// if (ExtRTC) is equivalent to saying, "if this has run before"
if (status){
//Optional: report time::
// Serial.print(hours); Serial.print(":"); Serial.print(minutes); Serial.print(":"); Serial.println(seconds);
updatetime = 1;
if (errorfound) {
TimeError = errorfound;
updatetime = 0;
}
if (updatetime)
{
HalfSecNow = (bcd2bin(seconds) * 2) + 1;
MinNow = minutes;
StdHrNow = hours;
StdDaysNow = days;
// Serial.println("OK, updating the time");
}
}
return status;
}
byte RTCseconds()
{ // get seconds from RTC, if present
// send request to receive data starting at register 0
Wire.beginTransmission(104); // 104 is DS3231 device address
Wire.send(0); // start at register 0
Wire.endTransmission();
Wire.requestFrom(104, 1); // request one byte (the seconds)
byte seconds = 200;
byte updatetime = 0;
while(Wire.available())
{
seconds = bcd2bin(Wire.receive());
}
return seconds;
}
byte AlarmTimeSnoozeMin;
byte AlarmTimeSnoozeHr;
byte snoozed;
inline byte bin2bcd (byte x) { // useful conversion function
return ((x/10)*16 + (x%10));
}
inline byte bcd2bin (byte x) { // another useful conversion function
return (x - ((x>>4)&15)*6);
}
inline boolean bcdBad (byte x) { // only check low nibble
if ((x&15)>9) return true;
return false;
}
inline byte bcdAdd (byte x, byte y) { // quick and dirty
// "out of range" results will be e.g. A0 for 100
// other parts of this program depend on this behavior
byte z = (x&15)+(y&15);
if (z<10) return (x + y);
return (x + y + 6);
}
inline byte bcdSub (byte x, byte y) { // quick and dirty
// "negative" results will be e.g. F9 for -1, F8 for -2...
if ((x&15)>=(y&15)) return (x - y);
return ((x - y) - 6);
}
unsigned long days2greg (unsigned int x) {
// input to this function is a day number with day 0 = Friday 1 March 1996
// output is 0x0WDDMMYY (ISO weekday number, then BCD date, BCD month, BCD year)
// not lexically sortable but who cares, not for calculations anyway
if ((xMaxValidDays)) return 0xDEADBEEF;
byte w=5;
w+=(x%7);
if (w>7) w-=7; // this gives us our weekday
byte y=0-4;
byte m=3;
byte d=1;
y+=(4*(x/1461)); // 4-year intervals
x%=1461;
// we can't use a modulo operation for 0 to 3 years
if (x>=730) {y+=2; x-=730;} // 2 years
if (x>=365) {y++; x-=365;} // 1 year
if (x>=306) {y++; m=1; x-=306;} // 10 months: advance to January
else if (x>=153) {m=8; x-=153;} // 5 months: advance to August
if (x>=122) {m+=4; x-=122;} // 4 months
if (x>=61) {m+=2; x-=61;} // 2 months
if (x>=31) {m++; x-=31;} // 1 month
// now we have the right year and month
d+=x; // the day of the month
// next, calculate output
unsigned long r = w;
r = (r<<8) + bin2bcd(d);
r = (r<<8) + bin2bcd(m);
r = (r<<8) + bin2bcd(y);
return r;
}
unsigned int greg2days (unsigned long x) { // inverse of days2greg
// Serial.println(x,HEX);
byte y; byte m; byte d; byte w;
unsigned int q; unsigned int r; unsigned int s;
y = x & 255; x = x >> 8;
m = x & 255; x = x >> 8;
d = x & 255; x = x >> 8;
w = x & 255;
if (bcdBad(y) || bcdBad(m) || bcdBad(d)) return 50000;
if ((w==0) || (w>7)) return 50000;
y = bcd2bin(y);
m = bcd2bin(m);
d = bcd2bin(d);
// Serial.println(y);
// Serial.println(m);
// Serial.println(d);
if ((y>99) || (m<1) || (m>12) || (d==0)) return 50000;
switch (m) {
case 4:
case 11:
case 9:
case 6:
if (d>30) return 50000;
break;
case 2:
if (((y%4)>0) && (d>28)) return 50000;
else if (d>29) return 50000;
break;
default:
if (d>31) return 50000;
}
// 50000 = an error code
// (year, month, day, and/or weekday is invalid or out of range)
// next we do some math
q = 45 + (y*12) + m; // count of months with month 0 = March 1996
r = 1461*(q/48); q%=48; // 4-year intervals
r += 365*(q/12); q%=12; // years
r += 153*(q/5); q%=5; // 5-month intervals
r += 61*(q/2); q%=2; // 2-month intervals
if (q) {r+=31; q--;} // one month
r += (d-1); // days within the month
// now r is a count of days with day 0 = Friday 1 March 1996
// Serial.println(r);
s = 5 + (r%7); if (s>7) s-=7; // s is our expected day of the week
// Serial.println(s);
if (s!=w) return 50001; // error code for unexpected day of the week
return r;
}
byte days2wknum (unsigned int x){
// input to this function is a day number with day 0 = Friday 1 March 1996
// output is week number in BCD format
// ISO week numbering is used
if ((xMaxValidDays)) return 0xEE;
x -= 306; // move start date to 1997-01-01 (Wed)
unsigned int z = (x+2) % 7; // days since most recent Monday (zero-based)
x = x - z + 3; // round to nearest Thursday
x %= 1461; // get rid of 4-year intervals
if (x >= 730) x -= 730; // 2 years
if (x >= 365) x -= 365; // 1 year
// now x is (zero-based) days since start of year
byte w = (x / 7) + 1; // ISO week number
return bin2bcd(w);
}
byte isDayInDST(unsigned int d){
// input to this function is a day number with day 0 = Friday 1 March 1996
// output: 0 = off all day, 1 = start date, 2 = end date, 3 = on all day
// rule used is: 2nd Sun. of March to 1st Sun. of Nov.
// this rule is current in the USA as of 2012
unsigned int w = (d + 5) % 7; // weekday 0 to 6, with 0 = Sunday
unsigned int y;
y = (d / 1461) * 4 ; d %= 1461;
if (d==1460) return 0; // special case leap day
y += (d / 365); d %= 365;
unsigned int yw = (4 + y + (y/4)) % 7; // weekday of 14 March (and 7 November), with 0 = Sunday
unsigned int s = 13 - yw; // start date
unsigned int e = 251 - yw; // end date
if (d==s) return 1;
if (d==e) return 2;
if ((d>s) && (d 119){
HalfSecNow -= 120;
MinNow = bcdAdd(MinNow, 0x01);
}
if (SettingTime) TimeError=0;
if (MinNow >= 0x60){
MinNow = bcdSub(MinNow, 0x60);
StdHrNow = bcdAdd(StdHrNow, 0x01);
}
if (StdHrNow >= 0x24) {
StdHrNow = bcdSub(StdHrNow, 0x24);
StdDaysNow++;
}
if (StdDaysNow < MinValidDays) StdDaysNow+=DaysLoop;
if (StdDaysNow > MaxValidDays) StdDaysNow-=DaysLoop;
byte DSTToday;
if (StdDaysNow == OldDSTDayCheck) DSTToday = OldDSTDayResult;
else {
DSTToday = isDayInDST(StdDaysNow);
OldDSTDayCheck = StdDaysNow;
OldDSTDayResult = DSTToday;
}
switch (isDayInDST(StdDaysNow)) {
case 0: DSTNow = false; break;
case 1: DSTNow = (StdHrNow >= 0x02); break;
case 2: DSTNow = (StdHrNow < 0x01); break;
case 3: DSTNow = true; break;
}
HrNow = StdHrNow;
DaysNow = StdDaysNow;
if (DSTNow) {
HrNow = bcdAdd(HrNow, 0x01);
if (HrNow >= 0x24) {
HrNow = bcdSub(HrNow, 0x24);
DaysNow++;
if (DaysNow>MaxValidDays) DaysNow=MinValidDays; // kludge
}
}
if (DaysNow != OldDays) {
GregNow = days2greg(DaysNow);
WkNumNow = days2wknum(DaysNow);
OldDays = DaysNow;
OldGreg = GregNow;
OldWkNum = WkNumNow;
unsigned long GregNowTemp = GregNow;
YearNow = (GregNowTemp & 255); GregNowTemp = GregNowTemp >> 8;
MonthNow = (GregNowTemp & 255); GregNowTemp = GregNowTemp >> 8;
DateNow = (GregNowTemp & 255); GregNowTemp = GregNowTemp >> 8;
WeekdayNow = (GregNowTemp & 255);
}
// ZeroHourBlankNow = 0;
// AMPM24HdisplayNow = ' ';
byte HalfSecNowTemp = HalfSecNow;
SpinnerNow = 0;
if (HalfSecNowTemp >= 60) {HalfSecNowTemp -= 60; SpinnerNow += 4;}
if (HalfSecNowTemp >= 30) {HalfSecNowTemp -= 30; SpinnerNow += 2;}
if (HalfSecNowTemp >= 15) {HalfSecNowTemp -= 15; SpinnerNow++;}
TwelveHrNow = bcd2bin(HrNow);
if (TwelveHrNow > 12) TwelveHrNow -= 12;
if (TwelveHrNow == 0) TwelveHrNow = 12;
HalfSecNowTemp = HalfSecNow;
BCDSecNow = 0;
if (HalfSecNowTemp >= 60) {HalfSecNowTemp -= 60; BCDSecNow += 0x30;}
if (HalfSecNowTemp >= 40) {HalfSecNowTemp -= 40; BCDSecNow += 0x20;}
if (HalfSecNowTemp >= 20) {HalfSecNowTemp -= 20; BCDSecNow += 0x10;}
BCDSecNow += (HalfSecNowTemp >> 1);
if ((SettingTime == 0) && (SettingAlarm==0) && (HalfSecNow==0))
{ // Only check once per minute
if (AlarmEnabled) {
if ((AlarmTimeHr == HrNow ) && (AlarmTimeMin == MinNow ))
{
AlarmNow = 1;
snoozed = 0;
NextAdvanceSound = microsCopy;
SoundSequence = 0;
}
else if (snoozed) {
if ((AlarmTimeSnoozeHr == HrNow ) && (AlarmTimeSnoozeMin == MinNow )) {
AlarmNow = 1;
snoozed = 0;
NextAdvanceSound = microsCopy;
SoundSequence = 0;
}
}
else TurnAlarmOff(); // stop alarm after 1 minute
}
// LastAlarmCheckMin = MinNow;
}
}
void CalculateNewAlarm (void)
{ // Update current display representation of the Alarm time
if (AlarmTimeMin >= 0x60) {
AlarmTimeMin = bcdSub(AlarmTimeMin, 0x60);
AlarmTimeHr = bcdAdd(AlarmTimeHr, 0x01);
}
if (AlarmTimeHr >= 0x24){
AlarmTimeHr = bcdSub(AlarmTimeHr, 0x24);
}
// ZeroHourBlankAlrm = 0;
AMPM24HdisplayAlrm = 'H';
snoozed = 0; // Recalculating alarm time *turns snoose off.*
// I (odometer) found this needless as well as very annoying, so I removed it
/* if (AlarmEnabled)
if ((AlarmTimeHr == HrNow ) && (AlarmTimeMin == MinNow ))
{
AlarmNow = 1;
NextAdvanceSound = 0;
SoundSequence = 0;
}
*/
}
void LoadShiftRegBuffers( void)
{
// Map the 5-character cotents of the ASCII Character Buffer into the 15 SPI output bytes
// (three bytes per LED character) needed to draw those characters.
byte alphaPosTemp;
byte j = 0;
while (j < 5)
{
alphaPosTemp = 3 * CharacterBuffer[j];
bufl1[j] = AlphaArray[alphaPosTemp++];
bufh1[j] = AlphaArray[alphaPosTemp++];
bufh2[j] = AlphaArray[alphaPosTemp];
j++;
}
}
void refreshDisplay (void)
{
byte k1, k2; // temporary register values
byte i, j; // loop counter
unsigned int onPeriod;
unsigned int offPeriod;
//MainBright can be 0 to MaxBright. 0 is OFF.
//MainBright = 1; // for test only
byte tempbright = MainBright;
if (VCRmode){
if (HalfSecNow & 2)
tempbright = 0;
}
if (tempbright == 0)
{
PORTA |= 64; // Blank LED driver
}
else if (tempbright <= 7)
{
// For low brightness levels, we use a very low duty cycle PWM.
// That's normally fine, but the Arduino interrupts in the background
// (for example, the one that keeps our millisecond timer accurate!)
// create tiny timing varations, and so these short "on" bursts
// can have *very* poor consistency, leading to a jittery, flickery
// display. To get around this, we temporarily turn off interrupts,
// for just long enough to turn on the LEDs for a few clock cycles
// (under 1 us). Once interrupts are back on, pending interrupt
// requests will be serviced, so we should not lose any time in the
// process. *However* take great care if you extend this
// "interrupt free" section of the code to any longer duration.
// Care taken. --odometer
switch (tempbright) {
// very non-linear
case 7:
i = 200;
while (i--) // Number of loops through the five digits (for low-power modes)
{
j = 5;
while (j--)
{
// I have no idea what's going on here
// I'm not much of a hardware guy -- odometer
AlphaWrite(bufl1[j],bufh1[j],bufh2[j]);
Latch();
PORTA &= ~(1 << j); // Enable character
//PORTA &= 191; // Enable LED Driver
k1 = PORTA & 191; // Enable LED Driver
k2 = PORTA | 64; // Blank LED driver
byte SREGtemp = SREG;
cli(); // Disable interrupts
PORTA = k1;
// All right, here goes nothing:
asm volatile("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
::); // that's 30
PORTA = k2;
SREG = SREGtemp; // reenable interrupts.
delayMicroseconds(4); // rest period
}
}
break;
case 6:
i = 200;
while (i--) // Number of loops through the five digits (for low-power modes)
{
j = 5;
while (j--)
{
// I have no idea what's going on here
// I'm not much of a hardware guy -- odometer
AlphaWrite(bufl1[j],bufh1[j],bufh2[j]);
Latch();
PORTA &= ~(1 << j); // Enable character
//PORTA &= 191; // Enable LED Driver
k1 = PORTA & 191; // Enable LED Driver
k2 = PORTA | 64; // Blank LED driver
byte SREGtemp = SREG;
cli(); // Disable interrupts
PORTA = k1;
// All right, here goes nothing:
asm volatile("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
::); // that's 16
PORTA = k2;
SREG = SREGtemp; // reenable interrupts.
delayMicroseconds(5); // rest period
}
}
break;
case 5:
i = 200;
while (i--) // Number of loops through the five digits (for low-power modes)
{
j = 5;
while (j--)
{
// I have no idea what's going on here
// I'm not much of a hardware guy -- odometer
AlphaWrite(bufl1[j],bufh1[j],bufh2[j]);
Latch();
PORTA &= ~(1 << j); // Enable character
//PORTA &= 191; // Enable LED Driver
k1 = PORTA & 191; // Enable LED Driver
k2 = PORTA | 64; // Blank LED driver
byte SREGtemp = SREG;
cli(); // Disable interrupts
PORTA = k1;
asm volatile("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
::);
PORTA = k2;
SREG = SREGtemp; // reenable interrupts.
delayMicroseconds(6); // rest period
}
}
break;
case 4:
i = 120;
while (i--) // Number of loops through the five digits (for low-power modes)
{
j = 5;
while (j--)
{
// I have no idea what's going on here
// I'm not much of a hardware guy -- odometer
AlphaWrite(bufl1[j],bufh1[j],bufh2[j]);
Latch();
PORTA &= ~(1 << j); // Enable character
//PORTA &= 191; // Enable LED Driver
k1 = PORTA & 191; // Enable LED Driver
k2 = PORTA | 64; // Blank LED driver
byte SREGtemp = SREG;
cli(); // Disable interrupts
PORTA = k1;
asm volatile("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
::);
PORTA = k2;
SREG = SREGtemp; // reenable interrupts.
delayMicroseconds(10); // rest period
}
}
break;
case 3:
i = 40;
while (i--) // Number of loops through the five digits (for low-power modes)
{
j = 5;
while (j--)
{
// I have no idea what's going on here
// I'm not much of a hardware guy -- odometer
AlphaWrite(bufl1[j],bufh1[j],bufh2[j]);
Latch();
PORTA &= ~(1 << j); // Enable character
//PORTA &= 191; // Enable LED Driver
k1 = PORTA & 191; // Enable LED Driver
k2 = PORTA | 64; // Blank LED driver
byte SREGtemp = SREG;
cli(); // Disable interrupts
PORTA = k1;
asm volatile("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
::);
PORTA = k2;
SREG = SREGtemp; // reenable interrupts.
delayMicroseconds(30); // rest period
}
}
break;
case 2:
i = 16;
while (i--) // Number of loops through the five digits (for low-power modes)
{
j = 5;
while (j--)
{
// I have no idea what's going on here
// I'm not much of a hardware guy -- odometer
AlphaWrite(bufl1[j],bufh1[j],bufh2[j]);
Latch();
PORTA &= ~(1 << j); // Enable character
//PORTA &= 191; // Enable LED Driver
k1 = PORTA & 191; // Enable LED Driver
k2 = PORTA | 64; // Blank LED driver
byte SREGtemp = SREG;
cli(); // Disable interrupts
PORTA = k1;
asm volatile("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
::);
PORTA = k2;
SREG = SREGtemp; // reenable interrupts.
delayMicroseconds(80); // rest period
}
}
break;
case 1:
i = 7;
while (i--) // Number of loops through the five digits (for low-power modes)
{
j = 5;
while (j--)
{
// I have no idea what's going on here
// I'm not much of a hardware guy -- odometer
AlphaWrite(bufl1[j],bufh1[j],bufh2[j]);
Latch();
PORTA &= ~(1 << j); // Enable character
//PORTA &= 191; // Enable LED Driver
k1 = PORTA & 191; // Enable LED Driver
k2 = PORTA | 64; // Blank LED driver
byte SREGtemp = SREG;
cli(); // Disable interrupts
PORTA = k1;
// All right, here goes nothing:
asm volatile("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
::);
PORTA = k2;
SREG = SREGtemp; // reenable interrupts.
delayMicroseconds(184); // rest period
}
}
break;
}
}
else
{ // Higher duty-cycle modes
if (tempbright >= MaxBright)
{
tempbright = MaxBright;
}
switch (MaxBright - tempbright) {
// logarithmic brightness scale
case 0: onPeriod = 163; offPeriod = 3; break;
case 1: onPeriod = 90; offPeriod = 76; break;
case 2: onPeriod = 50; offPeriod = 116; break;
case 3: onPeriod = 28; offPeriod = 138; break;
default: onPeriod = 15; offPeriod = 151; break;
}
i = 14;
while (i--) // Number of loops through the five digits (for high-power modes)
{
j = 5;
while (j--)
{
AlphaWrite(bufl1[j],bufh1[j],bufh2[j]);
Latch();
PORTA &= ~(1 << j); // Enable character
//PORTA &= 191; // Enable LED Driver
k1 = PORTA & 191; // Enable LED Driver
k2 = PORTA | 64; // Blank LED driver // Was PORTA |= _BV(6);
PORTA = k1;
delayMicroseconds(onPeriod);
PORTA = k2;
if (offPeriod != 0)
delayMicroseconds(offPeriod);
}
}
}
}
void TurnAlarmOff (void)
{
AlarmNow = 0;
noTone(13);
PORTD |= 32; // Turn off speaker
}
void setup() // run once, when the sketch starts
{
PORTA = 127;
PORTB = buttonmask; // Pull-up resistors for buttons
PORTC = 3; // PULL UPS for I2C
PORTD = 0;
DDRD = _BV(6) | _BV(7); // LED on PD6, PD7
DDRC = _BV(2); // Latch pin, PC2
DDRB = _BV(4) | _BV(5) | _BV(7); // SS, SCK, MOSI are outputs
DDRA = 127; // Outputs: PA0-PA5 ("rows") and Blank pin, PA6
//ENABLE SPI, MASTER, CLOCK RATE fck/4:
SPCR = _BV(SPE) | _BV(MSTR); // Initialize SPI, fast!
SPSR |= 1; // enable double-speed mode!
AlphaWrite(0,0,0);
Latch();
TimeError = 0; // will be zero until we find something wrong
countHalfSec = false;
timeIsRaw = false;
HalfSecNow = 0;
SpinnerNow = 0;
TwelveHrNow = 0;
BCDSecNow = 0x00;
MinNow = 0x00;
StdHrNow = 0x00;
// Saturday 1 January 2000
StdDaysNow = 1401;
DSTNow = false;
OldDays = 55555;
OldGreg = 0xFEEDFACE;
OldWkNum = 0xEE;
OldDSTDayCheck = 56789;
OldDSTDayResult = 99;
SerialDisplayMode = 0;
IdleTimeEnded = 1;
AMPM24HdisplayAlrm = ' ';
VCRmode = 0; // Time is NOT yet set, but this should be zero until after "hello world."
// LastAlarmCheckMin = 0;
HoldLoopCount = 0;
EEReadSettings(); // Read stored settings from EEPROM.
updateNightLight();
VCRmode = 1; // Time is NOT yet set.
//FactoryResetDisable = 0;
PINBLast = PINB & buttonmask;
HoldTimeSet = 0;
HoldAlarmSet = 0;
HoldOption = 0;
MomentaryOverridePlus = 0;
MomentaryOverrideMinus = 0;
SettingTime = 0; // Normally 0, while not setting time.
SettingAlarm = 0; // Same deal.
PORTA |= _BV(6); // Blank LED driver
DisplayWordSequence("HELLO",740000UL);
while ((long)(micros() - WordStopTime) < 0){
refreshDisplay ();
DisplayWordMode = 0;
}
delay (10);
DisplayWordSequence("WORLD",740000UL);
while ((long)(micros() - WordStopTime) < 0){
refreshDisplay ();
DisplayWordMode = 0;
}
delay (10);
microsCopy = micros();
LastTime = microsCopy;
VCRmode = 1; // Time is NOT yet set.
Serial.begin(19200);
// DateTime.sync(0);
Wire.begin();
ExtRTC = 0;
// Check if RTC is avaiiable, and use it to set the time if so.
ExtRTC = RTCgetTime();
// If no RTC is found, no attempt will be made to use it thereafter.
if (ExtRTC) // If time is already set from the RTC...
VCRmode = 0;
CalculateNewTime();
CalculateNewAlarm();
TurnAlarmOff();
snoozed = 0;
noTone(13);
PORTD |= 32; // Turn off speaker
} // End Setup
void loop()
{
//byte HighLine, LowLine;
byte PINBcopy;
//byte alphaPosTemp;
byte i,j; // Dummy indices
microsCopy = micros();
PINBcopy = PINB & buttonmask;
if (PINBcopy != PINBLast) // Button change detected
{
endIdleTime = microsCopy + IdleDelay; // Idle time for EEPROM purposes is 5 seconds.
IdleTimeEnded = 0;
VCRmode = 0; // End once any buttons have been pressed...
//TimeSinceButton = 0;
if ((PINBcopy & 1) && ((PINBLast & 1) == 0))
{ //"Snooze" / Set Alarm Time Button was pressed and just released!
if (SettingAlarm) {
SettingAlarm = 0; // End alarm time display/setting mode, when the "Snooze" button is released.
}
else if (AlarmNow)
{
snoozed = 1;
TurnAlarmOff();
DisplayWordSequence("SNOOZ",2500000UL);
AlarmTimeSnoozeHr = HrNow;
AlarmTimeSnoozeMin = bcdAdd(MinNow, 0x09); // Nine minutes, from time *snooze button pressed*
if (AlarmTimeSnoozeMin >= 0x60) {
AlarmTimeSnoozeMin = bcdSub(AlarmTimeSnoozeMin, 0x60);
AlarmTimeSnoozeHr = bcdAdd(AlarmTimeSnoozeHr, 0x01);
}
if (AlarmTimeSnoozeHr >= 0x24) {
AlarmTimeSnoozeHr = bcdSub(AlarmTimeSnoozeHr, 0x24);
}
}
}
if ((PINBcopy & 2) && ((PINBLast & 2) == 0))
{ //"Time" / Alarm On/off Button was pressed and just released!
if ((AlarmNow) || (snoozed)){ // Just Turn Off Alarm
TurnAlarmOff();
snoozed = 0;
}
else if (SettingTime) {
// We *have* been setting the time, but have just released the button.
SettingTime = 0; // Turn off time-setting mode.
if (ExtRTC) // Write the new time to the RTC, RIGHT NOW.
{ // Or not.
// RTCsetTime(StdDaysNow,StdHrNow,MinNow,bin2bcd(HalfSecNow/2));
timeIsRaw = true;
}
}
else {
// Normal adjustment mode.
if (AlarmEnabled)
AlarmEnabled = 0;
else
AlarmEnabled = 1;
}
}
if ((PINBcopy & 12 == 12) && ((PINBLast & 12)<12)) {
if (HoldOption) { // + and - down, together
MomentaryOverridePlus = 1;
MomentaryOverrideMinus = 1; // since we've detected a hold-down condition.
HoldOption = 0;
HourMode++;
if (HourMode >= 3) HourMode = 0;
}
}
if ((PINBcopy & 4) && ((PINBLast & 4) == 0)) //"+" Button was just released!
{
if ( MomentaryOverridePlus)
{
MomentaryOverridePlus = 0;
// Ignore this transition if it was part of a hold sequence.
}
else
{
if ((PINBcopy & 2) == 0) { // Time-setting button is currently depressed
SettingTime = 1; // Flag that we are now changing the time.
MinNow = bcdAdd(MinNow, 0x01);
HalfSecNow=0; // set seconds to zero if individual setp
LastTime=microsCopy; // allow *really* fine adjustment of time
CalculateNewTime();
}
else if ((PINBcopy & 1) == 0) { // Alarm-setting button is currently depressed
if (AlarmEnabled) {
SettingAlarm = 1; // Individual step mode
AlarmTimeMin = bcdAdd(AlarmTimeMin, 0x01); // Advance the Alarm time!
CalculateNewAlarm();
}
}
else {
// Brightness control mode
if (MainBright < MaxBright) {
MainBright++;
}
}
}
}
if ((PINBcopy & 8) && ((PINBLast & 8) == 0))
{ //"-" Button was pressed and just released!
if ( MomentaryOverrideMinus)
{
MomentaryOverrideMinus = 0;
// Ignore this transition if it was part of a hold sequence.
}
else
{
if ((PINBcopy & 2) == 0) { // Time-setting button is currently depressed
SettingTime = 1; // Declare that we are in individual step mode
HalfSecNow=0; // if in individual step mode, zero the seconds
LastTime=microsCopy; // allow *really* fine adjustment of time
StdHrNow = bcdAdd(StdHrNow, 0x23);
MinNow = bcdAdd(MinNow, 0x59);
StdDaysNow--;
CalculateNewTime();
}
else if ((PINBcopy & 1) == 0) { // Alarm-setting button is currently depressed
if (AlarmEnabled) {
SettingAlarm = 2; // Individual step mode
AlarmTimeHr = bcdAdd(AlarmTimeHr, 0x23);
AlarmTimeMin = bcdAdd(AlarmTimeMin, 0x59);
CalculateNewAlarm();
}
}
else { //Normal brightness adjustment mode
if (MainBright > 0)
MainBright--;
}
}
}
}
PINBLast = PINBcopy;
if ((PINBcopy) == 3) { // "+" and "-" are down.
HoldOption = 1; // We are holding for option setting mode.
HoldTimeSet = 0;
HoldAlarmSet = 0;
}
if (((long)(microsCopy - LastTime) >= ClockRateAdj) && (countHalfSec == false))
{
countHalfSec = true;
// Check to see if any buttons are being held down:
if (( PINBcopy) == buttonmask)
{ // No buttons are pressed.
// Reset the variables that check to see if buttons are being held down.
HoldTimeSet = 0;
HoldOption = 0;
HoldAlarmSet = 0;
NumAdvances = 0;
// FactoryResetDisable = 1;
// Save EEPROM if updated.
if ((long)(microsCopy - endIdleTime) > 0)
{
if (IdleTimeEnded == 0){
EESaveSettings();
IdleTimeEnded = 1;
}
}
}
else
{ // At least one button is down!
// Note which buttons are being held down
if ((( PINBcopy) == 10) ||(( PINBcopy) == 6)) // Alarm-time set is down
{ // Alarm button is down, and so is EITHER + or -.
HoldAlarmSet++;
HoldOption = 0;
HoldTimeSet = 0;
if (HoldAlarmSet==1) NextAdvance = microsCopy;
}
if ((( PINBcopy) == 9) ||(( PINBcopy) == 5)) //Time-set is pressed down.
{ // Time button is down, and so is EITHER + or -.
HoldTimeSet++;
HoldOption = 0;
HoldAlarmSet = 0;
if (HoldTimeSet==1) NextAdvance = microsCopy;
}
if (( PINBcopy) == 12) // "time" and "alarm" are down.
{
HoldTimeSet = 0;
HoldAlarmSet = 0;
HoldOption = 0;
}
}
if (HoldAlarmSet > 2)
{
MomentaryOverridePlus = 1; // Override momentary-action of switches
MomentaryOverrideMinus = 1; // since we've detected a hold-down condition.
//MomentaryOverrideSetAlarm = 1;
if (HoldAlarmSet > 250)
HoldAlarmSet = 250; // prevent overflow
}
if (HoldTimeSet > 2)
{
MomentaryOverridePlus = 1; // Override momentary-action of switches
MomentaryOverrideMinus = 1; // since we've detected a hold-down condition.
// MomentaryOverrideSetTime = 1;
SettingAlarm = 0;
if (HoldTimeSet > 250)
HoldTimeSet = 250; // prevent overflow
}
}
if (( PINBcopy) == buttonmask) {
HoldTimeSet = 0;
HoldOption = 0;
HoldAlarmSet = 0;
NumAdvances = 0;
//FactoryResetDisable = 1;
}
else { // Other "Immediate" actions if buttons are being held
// Detect if + or - is released while scanning time (real or alarm) forwards or backwards.
if (( PINBcopy & 12) == 12){
HoldAlarmSet = 0;
HoldTimeSet = 0;
NumAdvances = 0;
ShowDateNow = 0;
}
if (((HoldAlarmSet > 0) || (HoldTimeSet > 0)) && ((long)(microsCopy - NextAdvance) > 0)) //Holding buttons to advance time settings...
{
if (( PINBcopy) == 10) // Alarm +
{
if (HoldAlarmSet > 2)
{
AlarmTimeMin = bcdAdd(AlarmTimeMin, 0x15); // Advance the Alarm time!
CalculateNewAlarm();
NumAdvances++;
}
if (NumAdvances <= 8) {
NextAdvance = microsCopy + 500000UL;
}
else if (NumAdvances <= 24) {
NextAdvance = microsCopy + 240000UL;
}
else {
NextAdvance = microsCopy + 110000UL;
}
}
if (( PINBcopy) == 6) // Alarm -
{
if (HoldAlarmSet > 2)
{
AlarmTimeHr = bcdAdd(AlarmTimeHr, 0x23);
AlarmTimeMin = bcdAdd(AlarmTimeMin, 0x45);
CalculateNewAlarm();
NumAdvances++;
}
if (NumAdvances <= 8) {
NextAdvance = microsCopy + 500000UL;
}
else if (NumAdvances <= 24) {
NextAdvance = microsCopy + 240000UL;
}
else {
NextAdvance = microsCopy + 110000UL;
}
}
if (( PINBcopy) == 9) // Time +
{
if (HoldTimeSet > 3)
{
if (NumAdvances < 128) {
MinNow = bcdAdd(MinNow, 0x15); // Advance the time!
} else if (NumAdvances < 288){
StdHrNow = bcdAdd(StdHrNow, 0x01);
} else if (NumAdvances < 322){
StdDaysNow++;
} else {
StdDaysNow += 14;
}
SettingTime = 1; // Flag that time is changing, so that we don't reset it by RTC.idl
CalculateNewTime();
NumAdvances++;
}
if (NumAdvances <= 8) {
NextAdvance = microsCopy + 640000UL;
}
else if (NumAdvances <= 32) {
NextAdvance = microsCopy + 240000UL;
}
else if (NumAdvances <= 128){ // 32 hours
NextAdvance = microsCopy + 80000UL;
}
else if (NumAdvances <= 288) { // 8 days
NextAdvance = microsCopy + 80000UL;
}
else if (NumAdvances <= 322){ // 42 days
NextAdvance = microsCopy + 240000UL;
ShowDateNow = 1;
} else if (NumAdvances <= 346 ) { // 54 weeks
NextAdvance = microsCopy + 320000UL;
} else {
NextAdvance = microsCopy + 80000UL;
}
}
if (( PINBcopy) == 5) // Time -
{
if (HoldTimeSet > 3)
{
SettingTime = 1; // Flag that time is changing, so that we don't reset it by RTC.
if (NumAdvances < 128) {
StdHrNow = bcdAdd(StdHrNow, 0x23);
MinNow = bcdAdd(MinNow, 0x45);
StdDaysNow--;
} else if (NumAdvances < 288) {
StdHrNow = bcdAdd(StdHrNow, 0x23);
StdDaysNow--;
} else if (NumAdvances < 322){
StdDaysNow--;
} else {
StdDaysNow-=14;
}
CalculateNewTime();
// Serial.print(YearNow,HEX); Serial.print("."); Serial.print(MonthNow,DEC); Serial.print("."); Serial.print(DateNow,HEX); Serial.print("(");
// Serial.print(WeekdayNow,DEC); Serial.println(")");
NumAdvances++;
}
if (NumAdvances <= 8) {
NextAdvance = microsCopy + 640000UL;
}
else if (NumAdvances <= 24) {
NextAdvance = microsCopy + 240000UL;
}
else if (NumAdvances <= 128){ // 32 hours
NextAdvance = microsCopy + 80000UL;
}
else if (NumAdvances <= 288) { // 8 days
NextAdvance = microsCopy + 80000UL;
}
else if (NumAdvances <= 322){ // 42 days
NextAdvance = microsCopy + 240000UL;
ShowDateNow = 1;
} else if (NumAdvances <= 346 ) { // 54 weeks
NextAdvance = microsCopy + 320000UL;
} else {
NextAdvance = microsCopy + 80000UL;
}
}
}
}
if (countHalfSec) { // this is what makes our clock tick
if (ExtRTC && (HalfSecNow==119) && ((MinNow&15)==9) && (SettingTime == 0) && (timeIsRaw==false)) {
byte s = RTCseconds();
if ((long)(microsCopy - LastTime) >= 10000000UL) s=201;
switch (s) {
case 57:
case 58:
case 59:
// do nothing
break;
case 0:
case 1:
countHalfSec = false;
LastTime = microsCopy;
HalfSecNow = s * 3;
MinNow = bcdAdd(MinNow, 0x01);
CalculateNewTime();
// Serial.println("10-minute check OK"); //
break;
default:
TimeError = 1; // Uh-oh! Something's not right...
countHalfSec = false;
LastTime += ClockRateAdj; // Microseconds carry into half seconds...
HalfSecNow++;
CalculateNewTime();
}
}
else {
countHalfSec = false;
LastTime += ClockRateAdj; // Microseconds carry into half seconds...
HalfSecNow++;
CalculateNewTime();
if (timeIsRaw && ((HalfSecNow & 1) == 0)) {
RTCsetTime(StdDaysNow,StdHrNow,MinNow,bin2bcd(HalfSecNow/2));
timeIsRaw = false;
// Serial.println("OK, wrote time to RTC"); //
}
}
}
if (AlarmNow ) // Visual display and sounds durign ALARM sequences
{
//SoundSequence
if ((long)(microsCopy - NextAdvanceSound) > 0){
if (AlarmTone == 3) // Siren Tone
{
if (SoundSequence < 200)
{
tone(13, 20 + 5 * SoundSequence, 20);
NextAdvanceSound = microsCopy + 10000UL;
SoundSequence++;
}
else if (SoundSequence == 200)
{
tone(13, 20 + 5 * SoundSequence, 2000);
NextAdvanceSound = microsCopy + 1500000UL;
SoundSequence++;
}
else {
NextAdvanceSound = microsCopy + 1000000UL;
SoundSequence = 0;
noTone(13);
PORTD |= 32; // Turn off speaker
}
}
else if (AlarmTone == 2) // Low Tone
{
if (SoundSequence < 8)
{
if (SoundSequence & 1)
{
tone(13, 100, 300);
NextAdvanceSound = microsCopy + 200000UL;
SoundSequence++;
}
else
{
NextAdvanceSound = microsCopy + 200000UL;
SoundSequence++;
noTone(13);
PORTD |= 32; // Turn off speaker
//
}
}
else
{
NextAdvanceSound = microsCopy + 1000000UL;
SoundSequence = 0;
noTone(13);
PORTD |= 32; // Turn off speaker
}
}
else if (AlarmTone == 1) // Med Tone
{
if (SoundSequence < 6)
{
if (SoundSequence & 1)
{
tone(13, 1000, 300);
NextAdvanceSound = microsCopy + 200000UL;
SoundSequence++;
}
else
{
NextAdvanceSound = microsCopy + 200000UL;
SoundSequence++;
noTone(13);
PORTD |= 32; // Turn off speaker
}
}
else
{
NextAdvanceSound = microsCopy + 1400000UL;
SoundSequence = 0;
noTone(13);
PORTD |= 32; // Turn off speaker
}
}
else if (AlarmTone == 0) // High Tone
{
if (SoundSequence < 6)
{
if (SoundSequence & 1)
{
tone(13, 2050, 400);
NextAdvanceSound = microsCopy + 300000UL;
SoundSequence++;
}
else
{
NextAdvanceSound = microsCopy + 200000UL;
SoundSequence++;
noTone(13);
PORTD |= 32; // Turn off speaker
}
}
else
{
NextAdvanceSound = microsCopy + 1000000UL;
SoundSequence = 0;
noTone(13);
PORTD |= 32; // Turn off speaker
}
}
}
}
if (DisplayWordMode)
{
if (SerialDisplayMode == 0){
if ((long)(microsCopy - WordStopTime) > 0)
DisplayWordMode = 0;
}
}
else {
if (((PINBcopy & 1) == 0)){
// when the "Alarm/Snooze" button is pressed ...
if (AlarmEnabled) {
// ... if the alarm is enabled, display the alarm time, ...
if (HourMode==0) {
CharacterBuffer[0] = ('H' - asciiOffset);
CharacterBuffer[1] = ((AlarmTimeMin&15) + numberOffset);
CharacterBuffer[2] = ((AlarmTimeMin>>4) + numberOffset);
CharacterBuffer[3] = ((AlarmTimeHr&15) + numberOffset);
CharacterBuffer[4] = ((AlarmTimeHr>>4) + numberOffset);
}
else {
CharacterBuffer[1] = ((AlarmTimeMin&15) + numberOffset);
CharacterBuffer[2] = ((AlarmTimeMin>>4) + numberOffset);
switch (AlarmTimeHr) {
case 0x12:
CharacterBuffer[3] = ('2' - asciiOffset);
CharacterBuffer[4] = ('1' - asciiOffset);
CharacterBuffer[0] = ('P' - asciiOffset);
break;
case 0x00:
CharacterBuffer[3] = ('2' - asciiOffset);
CharacterBuffer[4] = ('1' - asciiOffset);
CharacterBuffer[0] = ('A' - asciiOffset);
break;
default:
if (AlarmTimeHr < 0x12) {
CharacterBuffer[3] = ((AlarmTimeHr & 15) + numberOffset);
CharacterBuffer[4] = (((AlarmTimeHr>=0x10) ? '1' : ' ') - asciiOffset);
CharacterBuffer[0] = ('A' - asciiOffset);
} else {
CharacterBuffer[3] = ((bcdSub(AlarmTimeHr, 0x12) & 15) + numberOffset);
CharacterBuffer[4] = (((AlarmTimeHr>=0x22) ? '1' : ' ') - asciiOffset);
CharacterBuffer[0] = ('P' - asciiOffset);
}
break;
}
}
LoadShiftRegBuffers();
}
else if ((PINBcopy & 4) == 0){
// show the year
CharacterBuffer[0] = ((YearNow&15) + numberOffset);
CharacterBuffer[1] = ((YearNow>>4) + numberOffset);
CharacterBuffer[2] = ('0' - asciiOffset);
CharacterBuffer[3] = ('2' - asciiOffset);
CharacterBuffer[4] = ('Y' - asciiOffset);
LoadShiftRegBuffers();
}
else if ((PINBcopy & 8) == 0){
// show the week number
CharacterBuffer[0] = ((WkNumNow&15) + numberOffset);
CharacterBuffer[1] = ((WkNumNow>>4) + numberOffset);
CharacterBuffer[2] = ('-' - asciiOffset);
CharacterBuffer[3] = ('K' - asciiOffset);
CharacterBuffer[4] = ('W' - asciiOffset);
LoadShiftRegBuffers();
}
else {
// ... otherwise, just show today's date
CharacterBuffer[0] = ((DateNow&15) + numberOffset);
CharacterBuffer[1] = ((DateNow>>4) + numberOffset);
CharacterBuffer[2] = (bcd2bin(MonthNow) + numberOffset);
CharacterBuffer[3] = (WeekArray[(WeekdayNow*2) + 1] - asciiOffset);
CharacterBuffer[4] = (WeekArray[WeekdayNow*2] - asciiOffset);
LoadShiftRegBuffers();
bufl1[2] |= 64; // dots for date
bufl1[3] |= 64;
}
}
else {
// "Normal" time display:
if (ShowDateNow) {
CharacterBuffer[0] = ((DateNow&15) + numberOffset);
CharacterBuffer[1] = ((DateNow>>4) + numberOffset);
CharacterBuffer[2] = (bcd2bin(MonthNow) + numberOffset);
CharacterBuffer[3] = ((YearNow&15) + numberOffset);
CharacterBuffer[4] = ((YearNow>>4) + numberOffset);
LoadShiftRegBuffers();
bufl1[2] |= 64; // dots for date
bufl1[3] |= 64;
} else {
if (HourMode==0) {
CharacterBuffer[0] = ((MinNow&15) + numberOffset);
CharacterBuffer[1] = ((MinNow>>4) + numberOffset);
if (TimeError) CharacterBuffer[2] = ('E' - asciiOffset);
else CharacterBuffer[2] = (SpinnerNow + spinnerOffset);
CharacterBuffer[3] = ((HrNow&15) + numberOffset);
CharacterBuffer[4] = ((HrNow>>4) + numberOffset);
LoadShiftRegBuffers();
} else {
if (HourMode==1) {
if (TimeError) {
CharacterBuffer[0] = ('E' - asciiOffset);
CharacterBuffer[1] = ('E' - asciiOffset);
}
else {
CharacterBuffer[0] = ((BCDSecNow&15) + numberOffset);
CharacterBuffer[1] = ((BCDSecNow>>4) + numberOffset);
}
CharacterBuffer[2] = ((MinNow&15) + numberOffset);
CharacterBuffer[3] = ((MinNow>>4) + numberOffset);
CharacterBuffer[4] = ((TwelveHrNow) + numberOffset);
LoadShiftRegBuffers();
bufl1[1] |= 128;
bufl1[2] |= 64;
bufl1[3] |= 128;
bufl1[4] |= 64;
} else {
if (TimeError) CharacterBuffer[0] = ('E' - asciiOffset);
else CharacterBuffer[0] = (((HrNow<0x12)?'A':'P') - asciiOffset);
CharacterBuffer[1] = ((MinNow&15) + numberOffset);
CharacterBuffer[2] = ((MinNow>>4) + numberOffset);
if (TwelveHrNow>=10) {
CharacterBuffer[3] = ((TwelveHrNow - 10) + numberOffset);
CharacterBuffer[4] = ('1' - asciiOffset);
} else {
CharacterBuffer[3] = ((TwelveHrNow) + numberOffset);
CharacterBuffer[4] = (' ' - asciiOffset);
}
LoadShiftRegBuffers();
bufl1[2] |= 128;
bufl1[3] |= 64;
}
}
}
}
// Add time delimiter (colon) for time display, whether that's "real" time or the alarm.
// No, don't show it. I commented this part out.
if (ShowDateNow) {
bufl1[2] |= 64;
bufl1[3] |= 64;
} else {
// bufl1[2] |= 128;
// bufl1[3] |= 64;
}
if (AlarmEnabled)
bufl1[4] |= 128; // Upper left dot
}
// Time (or word) to display is now computed.
// Now is the place in the loop when we switch gears, and
// actually light up the LEDs. :)
refreshDisplay();
// Can this sync be tried only once per second?
if( getPCtime()) { // try to get time sync from pc
if(TimeFromPC) { // update clocks if time has been synced
DisplayWordSequence("SYNC ",1000000UL);
HalfSecNow = (TimeFromPC%60)*2;
MinNow = bin2bcd((TimeFromPC/60)%60);
StdHrNow = bin2bcd((TimeFromPC/3600)%24);
StdDaysNow = (TimeFromPC/86400)-DaysOffset1970;
if ((StdDaysNow >= MinValidDays) && (StdDaysNow <= MaxValidDays)){
CalculateNewTime();
RTCsetTime(StdDaysNow,StdHrNow,MinNow,bin2bcd(HalfSecNow/2));
VCRmode = 0;
TimeError = 0;
}
else {
TimeError = 1;
}
}
}
}