/************** Tick Tock Tock **************/ /* Arduino code for the SerialLCD portion of the Tick Tock Tock project written by Steven Lehrburger 25 June 2008 for the Sociable Objects class at ITP taught by Rob Faludi */ #include #define deviceID '1' // Allows the radios in broadcast mode to identify each other #define WATCH '0' #define LCD '1' #define COMP '2' #define STARTBYTE '*' // a unifrom startbyte #define CONFBYTE 'R' // and confirmation byte #define NUM_TIME_UNITS 7 // centruy, year, month, day, hours, minutes, seconds #define statusPin 13 #define lcdTXPin 2 #define lcdRXPin 3 long initialMillis; // this will be set later int initialDateTimeData[7]; // dateTimeData in ccyymmddHHMMSS format int currentDateTimeData[7]; SoftwareSerial lcdSerial = SoftwareSerial(lcdRXPin, lcdTXPin); // use SoftwareSerial for the LCD // The setup gets the initial time from the computer void setup() { pinMode(statusPin, OUTPUT); pinMode(lcdRXPin, INPUT); pinMode(lcdTXPin, OUTPUT); lcdSerial.begin(9600); Serial.begin(9600); SetLCDDebug("----------------", "----------------"); blink(statusPin, 3); backlightOn(); // wait until you have a time boolean keepLooping = true; while (keepLooping) { if (CheckBufferForTime(COMP)) { keepLooping = false; } delay(100); } Serial.flush(); SendConfirmation(); } // The loop continually updates the LCD with a recomputed current time. // (Slightly inefficient, but simple, and we aren't really saving computing power for anything) void loop() { delay(20); RecursiveDateTimeUpdate(6, ((millis() - initialMillis) / 1000)); SetLCDTimeDate(); // if we receive a time at any point we should store it and then clear the buffer in case it was sent multiple times if (CheckBufferForTime(WATCH)) { while (Serial.available() > 0) { Serial.flush(); delay(50); } } } // Sends a confirmation ten times that includes a startbyte, a device ID, and a confirmation byte void SendConfirmation() { for (int i = 0; i < 10; i++) { SendByte(STARTBYTE); SendByte(deviceID); SendByte(CONFBYTE); delay(50); } } // Check's the buffer for a series of bytes that corresponds to a time. // It looks for a Time from a specific sender, depending on if it is being set initially or is running. boolean CheckBufferForTime(char senderID) { if (Serial.available() > 0) { int inByte = Serial.read(); // first make sure there is a valid startbyte - this will allow us to read junk out of the buffer until a string starts if (inByte == STARTBYTE) { int recSenderID = Serial.read(); if(recSenderID == senderID) { initialMillis = millis(); // get the initial milliseconds for (int i = 0; i < NUM_TIME_UNITS; i++) { initialDateTimeData[i] = currentDateTimeData[i] = Serial.read(); } // store each byte in the proper array return(true); // let the calling function know we stored a string } } } return(false); } /************** XBee configuration **************/ // Configure's the XBee with the proper addresses void configureXBee() { Serial.flush(); // put the radio in command mode: delay(1010); Serial.print("+++"); delay(1010); WaitForCR(); // everyone is broadcasting! Serial.print("ATRE, DH0, DLFFFF\r"); WaitForCR(); // but deviceID's determine sender and behavior Serial.print("ATMY"); Serial.print(deviceID); Serial.print("\r"); WaitForCR(); Serial.print("ATIDBCDE\r"); WaitForCR(); Serial.print("ATCN\r"); delay(1000); Serial.flush(); } // Waits for the radio to respond to an AT command with "Ok/r" void WaitForCR() { int thisByte = 0; while (thisByte != '\r') { if (Serial.available() > 0) { thisByte = Serial.read(); } } } // Blink the specified LED the specified number of times void blink(int pinNum, int howManyTimes) { for (int i=0; i< howManyTimes; i++) { digitalWrite(pinNum, HIGH); delay(200); digitalWrite(pinNum, LOW); delay(200); } } //Easier to read than the Serial.print, and it used to have a delay void SendByte(int byteToSend) { Serial.print(byteToSend, BYTE); } /************** Recursive function for keeping track of time **************/ // (same as for Watch) // The LCD will need to keep track of time on its own. To do this it must constantly // convert its own internal millis() count to a standard time, based off of the millis() // when the currently set time was received. // This is a relatively complex task, and I wrote a recursive function to handle it. (An example is below.) void RecursiveDateTimeUpdate(int index, int totalToCarry) { // the first time it is called, it will be passed 6 (the index of the seconds) // and the difference in millis over 1000, which is the total number of seconds to handle if (index == 0) { currentDateTimeData[index] = initialDateTimeData[index] + totalToCarry; // we assume we won't get beyond the year 9999.. if we do, we can have a Y10k bug scare (and a big party) } else { // the recursive case int totalToPass = (initialDateTimeData[index] + totalToCarry) / NumSubdivisionsForIndex(index); // first, figure out how many of the current unit we are going to have left over to pass to the next larger unit // note the decimal will be truncated! currentDateTimeData[index] = initialDateTimeData[index] + totalToCarry - (NumSubdivisionsForIndex(index) * totalToPass); // then we know the current value for the current unit is the initial plus the total to carry minues the number of // the current units that we are going to pass recursively as the next units RecursiveDateTimeUpdate(index - 1, totalToPass); // and then we can make the recursive call } } /* Partial Example So let's say we have 5000 seconds in our first call to the function that looks like RecursiveDateTimeUpdate(6, 50000) First, we need to determine how many of those seconds are minutes. So take however many seconds we currently have from initialTimeDateData, say, 34 and 5000, divide by 60 to get 83.9, and truncate the decimal to get the 83 minutes that will be recursively passed. Then get that decimal back by taking the initial number of seconds, adding the 5000, and subtracting 83*60 = 4980 to get 54 seconds left over. And then recurse to the next level. */ // Each unit of time divides differently into the next larger unit, and this handles those differences int NumSubdivisionsForIndex(int index) { if (index == 6) return(60); if (index == 5) return(60); if (index == 4) return(24); if (index == 3) return(DaysInCurrentMonth()); if (index == 2) return(12); if (index == 1) return(100); } // And each month has a different number of days int DaysInCurrentMonth () { // if ((currentDateTimeData[2] == 3) || // April (currentDateTimeData[2] == 4) || // June (currentDateTimeData[2] == 8) || // September (currentDateTimeData[2] == 10)) { // November return(30); } else if ((currentDateTimeData[2] == 1) && ((currentDateTimeData[1] % 4) == 0)) { return(29); // February (leap year) } else if ((currentDateTimeData[2] == 1) && ((currentDateTimeData[1] % 4) != 0)) { return(28); // February (normal year) } else { return(31); // all others } } /************** Controlling the Serial LCD from Sparkfun **************/ //Uses the below helper functions to print both the tiem and date to the LCD void SetLCDTimeDate() { selectLineOne(); delay(20); SetLCDTime(); selectLineTwo(); delay(20); SetLCDDate(); delay(20); } //Prints the current time to the current LCD line in the form "HH:MM:SS" void SetLCDTime() { PrintTwoDigitValue(4); lcdSerial.print(":"); PrintTwoDigitValue(5); lcdSerial.print(":"); PrintTwoDigitValue(6); lcdSerial.print(" "); } //Prints the current date to the LCD line in the form "MM/DD/YYYY" void SetLCDDate() { PrintTwoDigitValue(2); lcdSerial.print("/"); PrintTwoDigitValue(3); lcdSerial.print("/"); PrintTwoDigitValue(0); PrintTwoDigitValue(1); lcdSerial.print(" "); } //The received bytes lose their leading zero, and this function prints that zero when necessary void PrintTwoDigitValue(int index) { if (currentDateTimeData[index] <= 9) { lcdSerial.print("0"); } if ((index == 2) || (index == 3)) { lcdSerial.print(currentDateTimeData[index] + 1); } else { lcdSerial.print(currentDateTimeData[index]); } } //Print two strings to the display for debugging purposes since SoftwareSerial wasn't set up void SetLCDDebug(char* line1, char* line2) { selectLineOne(); delay(20); lcdSerial.print(line1); selectLineTwo(); delay(20); lcdSerial.print(line2); delay(20); } //The following functions were provided by Sparkfun for use with the serial LCD void selectLineOne(){ //puts the cursor at line 0 char 0. lcdSerial.print(0xFE, BYTE); //command flag lcdSerial.print(128, BYTE); //position } void selectLineTwo(){ //puts the cursor at line 0 char 0. lcdSerial.print(0xFE, BYTE); //command flag lcdSerial.print(192, BYTE); //position } void clearLCD(){ lcdSerial.print(0xFE, BYTE); //command flag lcdSerial.print(0x01, BYTE); //clear command. } void backlightOn(){ //turns on the backlight lcdSerial.print(0x7C, BYTE); //command flag for backlight stuff lcdSerial.print(157, BYTE); //light level. } void backlightOff(){ //turns off the backlight lcdSerial.print(0x7C, BYTE); //command flag for backlight stuff lcdSerial.print(128, BYTE); //light level for off. } void serCommand(){ //a general function to call the command flag for issuing all other commands lcdSerial.print(0xFE, BYTE); }