// Ugi's Dawn-Light Alarm Clock Sketch. // Test sketch for RTC module with 4-digit display but set from serial (no inputs) // MIT Licence // Ugi Dec 2012 // Written for Arduino 022 running on ATmega328. // We need the following libraries: // Wire // Arduino Pin connections: // A0 - Sw1 - normally pulled low through 10K // A1 - Sw2 - normally pulled low through 10K // A2 - free // A3 - free // A4 - 1307 (I2C data) // A5 - 1307 (I2C clock) // D0 - Serial Tx // D1 - Serial Rx // D2 - Rotary encoder "A" - LHS // D3 - Rotary encoder "B" - RHS // D4 - Encoder press Switch - normally pulled low throu' 10K // D6 - Buzzer // D7 - 7219 Clock // D8 - 7219 Data // D9 - 7219 Latch // General clock settings: #include - // usees analogue pins A4 (SDA - pin 27) & A5 (SCL- pin 28) for 2-wire data. const byte DS1307_Address = 0x68; byte seconds = 45, oldseconds=0, minutes=52, hours=23, date=8, month=12, day=4, year=11, protect = 0; // defaults just to start somewhere. byte NEWseconds = 45, NEWminutes=52, NEWhours=23, NEWdate=8, NEWmonth=12, NEWday=4, NEWyear=11 ; // These are for setting. Probably don't need to be global any more. // 4 - digit display control settings: // Our 4-digit display is setup as in MAX 7219 datasheet but only the DP for digit 3 is wired to anything - use other (external) LEDs as indicators const int fourDigitClock = 7; // 4-digit clock pin to control 7219 const int fourDigitData = 8; // 4-digit data pin to control 7219 const int fourDigitLatch = 9; // 4-digit latch pin to control 7219 boolean fourDigitBlink = false; // Redundant? **** int LEDintensity = 3; // Intensity of display set by 7219 - range 0 to 15 boolean LEDcontrol = false; // This may now be redundant... **** unsigned long blinkTime = 0; // Used to control blinking of the HH:MM colon separator. // Display Charater Definitions: //Segments numbered clockwise from top MSB first with middle bar last. This takes 7 bits - MSB will be for decimal / indicator. // These are the characters that I consider unambiguously legible on a 7-seg display. Others might be contrived. Of these, W and M are the most missed. const byte character[36] = { B1111110, //0 B0110000, //1 B1101101, //2 B1111001, //3 B0110011, //4 B1011011, //5 B1011111, //6 B1110000, //7 B1111111, //8 B1111011, //9 B1000000, // Top Dash - 10 B0000001, // Middle Dash - 11 B0001000, // Bottom Dash - 12 B0000000, // Blank - 13 B1110111, // A - 14 // B - 8 B0011111, // b - 15 B1001110, // C - 16 B0001101, // c - 17 B0111101, // d - 18 B1001111, // E - 19 B1000111, // F - 20 B1111011, // g - 21 B0110111, // H - 22 B0010111, // h - 23 B0010000, // i - 24 B0001110, // L - 25 // l - 1 B0010101, // n - 26 // O - 0 B0011101, // o - 27 B1100111, // P - 28 B1100111, // q - 29 B0000101, // r - 30 // S - 5 B0001111, // t - 31 B0111110, // U - 32 B0011100, // u - 33 B0111011, // y - 34 B1101101, // Z - 35 }; // Rotary encoder interface const int rotX = 2; const int rotY = 3; const int rotSw = 4; // have to start using some analogue pins for Switches 'cos short of digital pins boolean rotSwState = false; unsigned long rotSwTime =0; byte encoded[10]={0,0,0,0,0,0,0,0,0,0}; byte currentEncode = 2; // General Switch controls const int switch1 = 14; // This is A0 but using it as a digital pin we can refer to it as D14 boolean switch1State = false; const int switch2 = 15; // A1 = D15 - We could read as analogue easily enough but this avoids the need to change the code between pins... boolean switch2State = false; unsigned long switch1Time =0, switch2Time=0; const int buzzer = 6; // Pin for buzzer - 5V 30ma driven directly. Any more current and we would need a transistor in there. void setup(){ Serial.begin(9600); Serial.println(" DS1307 RTC based clock - Ugi Jan 2012"); delay(2000); // Give RTC some time to settle. Wire.begin(); // Need for RTC fourDigitSetup(); encoderSetup(); encoded[2]=0; pinMode(buzzer, OUTPUT); RTCsetFromSerial(); } void loop() { RTCread(); RTCprintTime(); // sends time to serial. fourDigitShowTime(); // Displayt time on 4-digit display fourDigitWriteData(LEDintensity,10); // Set intensity. Probably don't need in main loop any more. // Switch monitoring: // Encoder Switch: boolean _newSwState = digitalRead(rotSw); // check for switch press & determine long or short: if( _newSwState && (_newSwState != rotSwState)){rotSwTime=millis();} // if new press then set timer. if( !_newSwState && (_newSwState != rotSwState)){ unsigned long _compMillis=millis()-rotSwTime; // by using subtraction, we should cope even if we get a time-rollover. if(_compMillis > 2000){ boolean _set = setFromFourDigit();} // long press - we want to set the time. else{ // short press - display date. showDate(); } } rotSwState=_newSwState; // First tactile switch - check for switch press etc _newSwState = digitalRead(switch1); if( _newSwState && (_newSwState != switch1State)){ switch1Time=millis(); } if( !_newSwState && (_newSwState != switch1State)){ // just released see if long or short and behave accordingly. unsigned long _compMillis=millis()-switch1Time; if(_compMillis > 2000){ // Event for long press of Switch1 goes here - alarm set routine } else{ // Event for short Press } } switch1State=_newSwState; // Second tactile switch (on A0) _newSwState = digitalRead(switch2); if( _newSwState && (_newSwState != switch2State)){switch2Time=millis();} // if new contact set timer. if( !_newSwState && (_newSwState != switch2State)){// Just released switch unsigned long _compMillis=millis()-switch2Time; if(_compMillis > 2000){ // Event for long press of Switch2 goes here - More obscure parameter set routine for things like display brighness and ramp time. } else{ } // short press change alarm armed state. } switch2State=_newSwState; } // End main loop // Various funcions below. Generally RTC then 4-digit display then lamp then alarm then eeprom then buzzer. // Read time from RTC into time variables. void RTCread(){ byte data[11]={0,0,0,0,0,0,0,0,0,0,0}; Wire.beginTransmission(DS1307_Address); Wire.send(0); // set register to beginning Wire.endTransmission(); Wire.requestFrom(DS1307_Address, (byte)10); // request 7 bytes from RTC into buffer. for (byte _loop=0; _loop<10; _loop++){ data[_loop]=(Wire.receive()); } seconds = (((data[0] & B01110000)>>4)*10)+(data[0] & B00001111); minutes = (((data[1] & B01110000)>>4)*10)+(data[1] & B00001111); hours = (((data[2] & B00110000)>>4)*10)+(data[2] & B00001111); day = (data[3] & B00000111); date = (((data[4] & B00110000)>>4)*10)+(data[4] & B00001111); month = (((data[5] & B00010000)>>4)*10)+(data[5] & B00001111); year = (((data[6] & B11110000)>>4)*10)+(data[6] & B00001111); } // Write current time to RTC void RTCwrite(){ // writes all current data to RTC - if you only want to write one parameter then just read the clock first! byte data[7]; data[0] = (seconds % 10) + ((seconds/10)<<4); data[1] = (minutes % 10) + ((minutes/10)<<4); data[2] = (hours % 10) + ((hours/10)<<4); data[3] = day; data[4] = (date % 10) + ((date/10)<<4); data[5] = (month % 10)+ ((month/10)<<4); data[6] = (year % 10) + ((year/10)<<4); Wire.beginTransmission(DS1307_Address); Wire.send(0); // set register to beginning for (byte _loop=0; _loop<7; _loop++){ Wire.send(data[_loop]); } Wire.endTransmission(); oldseconds=seconds; } // End RTC Write function // Print current time to Serial... This can probably come out. void RTCprintTime(){ RTCread(); Serial.print("Time = "); Serial.print(hours, DEC); Serial.print(":");Serial.print(minutes, DEC);Serial.print(":"); Serial.println(seconds, DEC); Serial.print("Date = "); Serial.print(date, DEC); Serial.print("/");Serial.print(month, DEC);Serial.print("/20"); Serial.print(year, DEC); } byte RTCgetSerial(byte _val, String _Str){ Serial.println(" "); Serial.println(" *** "+_Str+" ***"); Serial.print("Current value = "); Serial.print(_val, DEC); Serial.println(" "+_Str); Serial.println("Enter new value (two digits e.g. '01'): "); Serial.flush(); byte NEW_val = -1; while (Serial.available() < 2){ delay(50); } NEW_val = ((Serial.read()-48)*10); NEW_val = NEW_val + (Serial.read()-48); Serial.flush(); Serial.print("New value = "); Serial.print(NEW_val, DEC); Serial.println(" "+_Str);Serial.println(" "); if (NEW_val >=0){return NEW_val;} else {return _val;} } boolean RTCconfirm(){ Serial.println(" "); Serial.println("Currently:"); RTCprintTime(); Serial.flush(); Serial.println(" "); Serial.println("Change to? (Y/N):"); Serial.print("time = "); Serial.print(NEWhours, DEC); Serial.print(":");Serial.print(NEWminutes, DEC);Serial.print(":"); Serial.println(NEWseconds, DEC); Serial.print("date = "); Serial.print(NEWdate, DEC); Serial.print("/");Serial.print(NEWmonth, DEC);Serial.print("/20");Serial.print(NEWyear, DEC); char _conf = -1; while (Serial.available()<1){ delay(50); } _conf = Serial.read(); Serial.flush(); Serial.print("confirm digit = "); Serial.println(_conf); if(_conf=='Y'){return true;} else{return false; } } boolean RTCsetFromSerial(){ // Get new time setting from serial port Serial.flush(); // avoid any random data in buffer. // See if we want to update char _input = 'X'; while (_input != 'R' && _input !='Q'){ while (Serial.available()<1){ RTCprintTime(); // We'll keep displaying current tiem and see whether it needs updating. Serial.println("Enter 'R' by serial input to re-set or Q to quit setting routine"); Serial.println(" "); delay(1000); } _input = Serial.read(); Serial.flush(); } // if(_input != 'R'){Serial.println(" "); Serial.println(" *** QUITTING ***"); Serial.println(" "); return false;} Serial.println(" "); Serial.println(" *** RESETTING RTC ***"); Serial.println(" "); RTCprintTime(); NEWyear = RTCgetSerial(year, "years"); RTCprintTime(); NEWday = RTCgetSerial(day, "days of the week"); RTCprintTime(); NEWmonth = RTCgetSerial(month, "months"); RTCprintTime(); NEWdate = RTCgetSerial(date, "days of the month"); RTCprintTime(); NEWhours = RTCgetSerial(hours, "Hours"); RTCprintTime(); NEWminutes = RTCgetSerial(minutes, "Minutes"); RTCprintTime(); NEWseconds = RTCgetSerial(seconds, "Seconds"); if (RTCconfirm()){ seconds = NEWseconds; minutes = NEWminutes; hours = NEWhours; date = NEWdate; month = NEWmonth; day = NEWday; year = NEWyear; RTCwrite(); Serial.println (" "); Serial.println (" *** RTC Updated ***"); return true; } else { Serial.println (" *** Update cancelled ***"); } return false; } // End of Set from Serial Routine void fourDigitSetup(){ pinMode(fourDigitLatch, OUTPUT); pinMode(fourDigitClock, OUTPUT); pinMode(fourDigitData, OUTPUT); fourDigitWriteData(1, 12); // turn off shutdown fourDigitWriteData(0, 15); // test mode off fourDigitWriteData(0,9); // Turn off decode mode fourDigitWriteData(3,11); // Set scan limit to our 4 digits. We can set it higher to reduce brighness if we wish. fourDigitWriteData(LEDintensity,10); // Set initial intensity byte _str[9]={22,19,25,25,0,13,13,13,13}; // say hello! fourDigitScroll(_str,9,250); } // End four-digit display setup // Write a digit to 4-digit display.. void fourDigitWrite(byte _char, byte _digit, boolean _dp){ byte _header = B00000000 | _digit; // redundant but we might need a header or other manipulation later. digitalWrite(fourDigitLatch, LOW); shiftOut(fourDigitData, fourDigitClock, MSBFIRST, _header);// Serial.print("Writing Header -");Serial.print(_header, HEX); byte _data = (character[_char]|(B10000000*_dp)); // Retrieve font definition and add decimal (colon separator or indicator LED) if appropriate. shiftOut(fourDigitData, fourDigitClock, MSBFIRST, _data );// Serial.print (" Writing Data -"); Serial.println (_data, BIN); digitalWrite(fourDigitLatch, HIGH); } // end character write // Need also to write control digits to appropriate registers. // Reg 10 - intensity // Reg 11 - scan limit // Reg 12 - shutdown control (1 to turn off shutdown) // Reg 15 - Test mode control (0 for test mode off) void fourDigitWriteData(byte _data, byte _register){ digitalWrite(fourDigitLatch, LOW); shiftOut(fourDigitData, fourDigitClock, MSBFIRST, _register); // Serial.print("register "); Serial.print(_register, HEX); shiftOut(fourDigitData, fourDigitClock, MSBFIRST, _data); // Serial.print(" data "); Serial.println(_data, BIN); digitalWrite(fourDigitLatch, HIGH); } // end data write // Display Time on four digit display: void fourDigitShowTime(){ byte _blink= true; if (seconds!=oldseconds){ // blink digit divider once a second. blinkTime = millis(); oldseconds=seconds; } _blink=true; if ((millis()-blinkTime)<250){_blink=false;} // blink for 250 ms every second. fourDigitWrite((hours/10),1, false); fourDigitWrite((hours%10),2, _blink); // colon separator on this digit fourDigitWrite((minutes/10),3, false); fourDigitWrite((minutes%10),4, false); // armed indicator on this digit } void fourDigitDisplayBlink(){ // flashes whole display off for 50 ms fourDigitWriteData(0,12); delay(50); fourDigitWriteData(1,12); } void encoderSetup(){ // set up encoder and switch input pins. pinMode(rotX,INPUT); pinMode(rotY,INPUT); pinMode(rotSw, INPUT); attachInterrupt(0, encoder, CHANGE); pinMode(switch1, INPUT); pinMode(switch2, INPUT); } // Function for updating variables from interrupt using the rotary encoder. Keep it short! // I have not needed software debouncing but if you do then un-comment the approprate lines below. //unsigned long debounce; // we have a 2ms software debounce implemented by this variable. void encoder(){ // This is called by interrupt every time the status of D2 changes. Must be either up or down! //if ((millis()-debounce)>1){ // if not changed state in last 2ms byte _A = (PIND & B1100); // read pins 2 & 3 - use direct port read for speed. byte _B = ((_A & B1000) >> 3); // Pin 3 state into _B _A = ((_A & B100)>>2); // Pin 2 state into _A if (_A == _B ){ // If A==B then A is following. If A != B then A is leading. encoded[currentEncode]++; // if your encoder is wired the other way just reverse this } else{ encoded[currentEncode]--; // and this } //} // debounce=millis(); // reset each time we interrupt so we wait 2ms from last "bounce" } // Need to be able to set the clock functions from the encoder and 4-digit display: boolean setFromFourDigit(){ encoded[1]=(year*2); byte _wasEncoded = currentEncode; currentEncode=1; byte _scrap = 0; while(digitalRead (rotSw)){delay(50);} // wait for button press to end while(!digitalRead (rotSw)){ fourDigitWrite(2,1, false); fourDigitWrite(0,2,false); fourDigitWrite((year/10),3, false); fourDigitWrite((year%10),4, false); // indicator on this digit delay(20); year=encoded[1]/2; if(year>99){year=99;} if(year<1){year=1;} // we don't need to go back to 2000 since it's 2011 now! _scrap++; if(_scrap>25){fourDigitDisplayBlink(); _scrap=0;} // blink every half-second - not working? } while(digitalRead (rotSw)){delay(50);} // wait for switch off encoded[1]=month*3; while(!digitalRead (rotSw)){ fourDigitWrite((date/10),1, false); fourDigitWrite((date%10),2,false); fourDigitWrite((month/10),3, false); fourDigitWrite((month%10),4, false); // indicator on this digit delay(20); month=encoded[1]/3; if(month>12){month=12; encoded[1]=37;} if(month<1){month=1; encoded[1]=4;} // there is no month zero! _scrap++; if(_scrap>25){fourDigitWrite(13,3,0); fourDigitWrite(13,4,0); delay(50); _scrap=0;} // blink every half-second - not working? } while(digitalRead (rotSw)){delay(50);} // wait for switch off encoded[1]=date*3; while(!digitalRead (rotSw)){ fourDigitWrite((date/10),1, false); fourDigitWrite((date%10),2,false); fourDigitWrite((month/10),3, false); fourDigitWrite((month%10),4, false); // indicator on this digit delay(20); date=encoded[1]/3; if(date>31){date=31; encoded[1]=94;} if(date<1){date=1; encoded[1]=4;} // there is no month zero! if(month==2 && (year%4)==0){if(date>29){date=29; encoded[1]=88;}} // Feb is a pain but at least we have already set the year! if(month==2 && (year%4)!=0){if(date>28){date=28; encoded[1]=85;}} if(month==4 || month==6 || month==9 || month==11){if(date>30){date=30; encoded[1]=91;}} _scrap++; if(_scrap>25){fourDigitWrite(13,1,0); fourDigitWrite(13,2,0); delay(50); _scrap=0;} // blink every half-second } while(digitalRead (rotSw)){delay(50);} // wait for switch off encoded[1]=(hours*3); while(!digitalRead (rotSw)){ fourDigitShowTime(); // indicator on this digit delay(20); hours=encoded[1]/3; if(hours>23){hours=0; encoded[1]=0;} if(hours<0){hours=23; encoded[1]=23*3+1;} _scrap++; if(_scrap>25){fourDigitWrite(13,1,0); fourDigitWrite(13,2,1); delay(50); _scrap=0;} // blink every half-second } while(digitalRead (rotSw)){delay(50);} // wait for switch off encoded[1]=(minutes*3); while(!digitalRead (rotSw)){ fourDigitShowTime(); // indicator on this digit delay(20); minutes=encoded[1]/3; if(minutes>59){minutes=0; encoded[1]=0;} if(minutes<0){minutes=59; encoded[1]=59*3+1;} _scrap++; if(_scrap>25){fourDigitWrite(13,3,1); fourDigitWrite(13,4,0); delay(50); _scrap=0;} // blink every half-second } while(digitalRead (rotSw)){delay(50);} // wait for switch off encoded[1]=(day*3); while(!digitalRead (rotSw)){ fourDigitWrite(13,1,0); // indicator on this digit fourDigitWrite(18,2,1); fourDigitWrite(day,3,0); fourDigitWrite(13,4,0); delay(20); day=encoded[1]/3; if(day>7){day=1; encoded[1]=0;} if(day<1){day=7; encoded[1]=7*3+1;} _scrap++; if(_scrap>25){fourDigitWrite(13,2,0); fourDigitWrite(13,3,1); delay(50); _scrap=0;} // blink every half-second } currentEncode=_wasEncoded; // set encoder back to controlling whatever it was doing before seconds=0; RTCwrite(); while(digitalRead (rotSw)){delay(50);} // wait for switch off } // End set time routine. void fourDigitScroll(byte*_str, byte _len, byte _del){ // takes array of char definitions ending in four 13s (spaces) byte _data[4]={13,13,13,13}; for (byte _loop=0; _loop<_len; _loop++){ _data[3]=_data[2]; _data[2]=_data[1]; _data[1]=_data[0]; _data[0]=_str[_loop]; fourDigitWrite(_data[3],1, false); fourDigitWrite(_data[2],2, false); fourDigitWrite(_data[1],3, false); fourDigitWrite(_data[0],4, false); delay(_del); } } void showDate(){ byte _str[25]={ (date/10), (date%10),11, // d - (month/10), (month%10),11,2,0, // mm - (year/10), (year%10),13,13,13,13, // 20yy - 18,14,34,13, // day day,13,13,13,13}; fourDigitScroll(_str, 23, 250); }