/* 7-17-2011 Spark Fun Electronics 2011 Nathan Seidle This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license). This is the firmware for BigTime, the wrist watch kit. It is based on an ATmega328 running with internal 8MHz clock and external 32kHz crystal for keeping the time (aka RTC). The code and system have been tweaked to lower the power consumption of the ATmeg328 as much as possible. The watch currently uses about 1.2uA in idle (non-display) mode and about 13mA when displaying the time. With a 200mAh regular CR2032 battery you should get 2-3 years of use! To compile and load this code onto your watch, select "Arduino Pro or Pro Mini 3.3V/8MHz w/ ATmega328" from the Boards menu. If you're looking to save power in your own project, be sure to read section 9.10 of the ATmega328 datasheet to turn off all the bits of hardware you don't need. BigTime requires the Pro 8MHz bootloader with a few modifications: Internal 8MHz Clock div 8 cleared Brown out detect disabled BOOTRST set BOOSZ = 1024 This is to save power and open up the XTAL pins for use with a 38.786kHz external osc. So the fuse bits I get using AVR studio: HIGH 0xDA LOW 0xE2 Extended 0xFF 3,600 seconds in an hour 1 time check per hour, 2 seconds at 13mA 3,598 seconds @ 1.2uA (3598 / 3600) * 0.0012mA + (2 / 3600) * 13mA = 0.0084mA used per hour 200mAh / 0.0084mA = 23,809hr = 992 days = 2.7 years We can't use the standard Arduino delay() or delaymicrosecond() because we shut down timer0 to save power We turn off Brown out detect because it alone uses ~16uA. 7-2-2011: Currently at 1.13uA 7-4-2011: Let's wake up every 8 seconds instead of every 1 to save even more power Since we don't display seconds, this should be fine We are now at ~1.05uA on average 7-17-2011: 1.09uA, portable with coincell power Jumps to 1.47uA every 8 seconds with system clock lowered to 1MHz Jumps to 1.37uA every 8 seconds with system clock at 8MHz Let's keep the internal clock at 8MHz since it doesn't seem to help to lower the internal clock. 8-11-2011: Adding display color so that production can more easily know what code is on the pre-programmed ATmega. 8-19-2011: Now we can print things like "red, gren, blue, yelo". */ #include //Needed for sleep_mode #include //Needed for powering down perihperals such as the ADC/TWI and Timers #define TRUE 1 #define FALSE 0 //Set the 12hourMode to false for military/world time. Set it to true for American 12 hour time. int TwelveHourMode = TRUE; //Set this variable to change how long the time is shown on the watch face. In milliseconds so 1677 = 1.677 seconds int show_time_length = 2000; int show_the_time = FALSE; //You can set always_on to true and the display will stay on all the time //This will drain the battery in about 15 hours int always_on = FALSE; long seconds = 55; int minutes = 12; int hours = 8; //Careful messing with the system color, you can damage the display if //you assign the wrong color. If you're in doubt, set it to red and load the code, //then see what the color is. #define RED 1 #define GREEN 2 #define BLUE 3 #define YELLOW 4 int systemColor = BLUE; int display_brightness = 15000; //A larger number makes the display more dim. This is set correctly below. //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //Pin definitions int digit1 = 9; //Display pin 1 int digit2 = 10; //Display pin 2 int digit3 = A0; //Display pin 6 int digit4 = A1; //Display pin 8 int segA = 6; //Display pin 14 int segB = 8; //Display pin 16 int segC = 5; //Display pin 13 int segD = 11; //Display pin 3 int segE = 13; //Display pin 5 int segF = 4; //Display pin 11 int segG = 7; //Display pin 15 int colons = 12; //Display pin 4 int ampm = 3; //Display pin 10 int theButton = 2; //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= //The very important 32.686kHz interrupt handler SIGNAL(TIMER2_OVF_vect){ seconds += 8; //We sleep for 8 seconds instead of 1 to save more power //seconds++; //Use this if we are waking up every second //Update the minutes and hours variables minutes += seconds / 60; //Example: seconds = 2317, minutes = 58 + 38 = 96 seconds %= 60; //seconds = 37 hours += minutes / 60; //12 + (96 / 60) = 13 minutes %= 60; //minutes = 36 //Do we display 12 hour or 24 hour time? if(TwelveHourMode == TRUE) { //In 12 hour mode, hours go from 12 to 1 to 12. while(hours > 12) hours -= 12; } else { //In 24 hour mode, hours go from 0 to 23 to 0. while(hours > 23) hours -= 24; } } //The interrupt occurs when you push the button SIGNAL(INT0_vect){ //When you hit the button, we will need to display the time //if(show_the_time == FALSE) show_the_time = TRUE; } void setup() { //To reduce power, setup all pins as inputs with no pullups for(int x = 1 ; x < 18 ; x++){ pinMode(x, INPUT); digitalWrite(x, LOW); } pinMode(theButton, INPUT); //This is the main button, tied to INT0 digitalWrite(theButton, HIGH); //Enable internal pull up on button //These pins are used to control the display pinMode(segA, OUTPUT); pinMode(segB, OUTPUT); pinMode(segC, OUTPUT); pinMode(segD, OUTPUT); pinMode(segE, OUTPUT); pinMode(segF, OUTPUT); pinMode(segG, OUTPUT); pinMode(digit1, OUTPUT); pinMode(digit2, OUTPUT); pinMode(digit3, OUTPUT); pinMode(digit4, OUTPUT); pinMode(colons, OUTPUT); pinMode(ampm, OUTPUT); //Power down various bits of hardware to lower power usage set_sleep_mode(SLEEP_MODE_PWR_SAVE); sleep_enable(); //Shut off ADC, TWI, SPI, Timer0, Timer1 ADCSRA &= ~(1< 12) hours -= 12; } else { //In 24 hour mode, hours go from 0 to 23 to 0. while(hours > 23) hours -= 24; } sei(); //Resume interrupts int combinedTime = (hours * 100) + minutes; //Combine the hours and minutes for(int x = 0 ; x < 10 ; x++) { displayNumber(combinedTime, TRUE); //Each call takes about 8ms, display the colon for about 100ms delayMicroseconds(display_brightness); //Wait before we paint the display again } for(int x = 0 ; x < 10 ; x++) { displayNumber(combinedTime, FALSE); //Each call takes about 8ms, turn off the colon for about 100ms delayMicroseconds(display_brightness); //Wait before we paint the display again } //If you're still hitting the button, then increase the time and reset the idleMili timeout variable if(digitalRead(theButton) == LOW) { idleMiliseconds = 0; buttonHold++; if(buttonHold < 10) minutes++; //Advance the minutes else { //Advance the minutes faster because you're holding the button for 10 seconds //Start advancing on the tens digit. Floor the single minute digit. minutes /= 10; //minutes = 46 / 10 = 4 minutes *= 10; //minutes = 4 * 10 = 40 minutes += 10; //minutes = 40 + 10 = 50 } } else buttonHold = 0; idleMiliseconds += 200; } } //This is a not-so-accurate delay routine //Calling fake_msdelay(100) will delay for about 100ms //Assumes 8MHz clock /*void fake_msdelay(int x){ for( ; x > 0 ; x--) fake_usdelay(1000); }*/ //This is a not-so-accurate delay routine //Calling fake_usdelay(100) will delay for about 100us //Assumes 8MHz clock /*void fake_usdelay(int x){ for( ; x > 0 ; x--) { __asm__("nop\n\t"); __asm__("nop\n\t"); __asm__("nop\n\t"); __asm__("nop\n\t"); __asm__("nop\n\t"); __asm__("nop\n\t"); __asm__("nop\n\t"); } }*/ //Given 1022, we display "10:22" //Each digit is displayed for ~2000us, and cycles through the 4 digits //After running through the 4 numbers, the display is turned off void displayNumber(int toDisplay, boolean displayColon) { #define DIGIT_ON HIGH #define DIGIT_OFF LOW for(int digit = 4 ; digit > 0 ; digit--) { //Turn on a digit for a short amount of time switch(digit) { case 1: digitalWrite(digit1, DIGIT_ON); break; case 2: digitalWrite(digit2, DIGIT_ON); if(displayColon == TRUE) digitalWrite(colons, DIGIT_ON); //When we update digit 2, let's turn on colons as well break; case 3: digitalWrite(digit3, DIGIT_ON); break; case 4: digitalWrite(digit4, DIGIT_ON); break; } //Now display this digit if(digit > 1) lightNumber(toDisplay % 10); //Turn on the right segments for this digit else if(digit == 1) { //Special case on first digit, don't display 02:11, display 2:11 if( (toDisplay % 10) != 0) //If we are on the first digit, and it's not zero lightNumber(toDisplay % 10); //Turn on the right segments for this digit } toDisplay /= 10; delayMicroseconds(2000); //Display this digit for a fraction of a second (between 1us and 5000us, 500-2000 is pretty good) //If you set this too long, the display will start to flicker. Set it to 25000 for some fun. //Turn off all segments lightNumber(10); //Turn off all digits digitalWrite(digit1, DIGIT_OFF); digitalWrite(digit2, DIGIT_OFF); digitalWrite(digit3, DIGIT_OFF); digitalWrite(digit4, DIGIT_OFF); digitalWrite(colons, DIGIT_OFF); digitalWrite(ampm, DIGIT_OFF); } } //Takes a string like "gren" and displays it, left justified //We don't use the colons, or AMPM dot, so they are turned off void displayLetters(char *colorName) { #define DIGIT_ON HIGH #define DIGIT_OFF LOW digitalWrite(digit4, DIGIT_OFF); digitalWrite(colons, DIGIT_OFF); digitalWrite(ampm, DIGIT_OFF); for(int digit = 0 ; digit < 4 ; digit++) { //Turn on a digit for a short amount of time switch(digit) { case 0: digitalWrite(digit1, DIGIT_ON); break; case 1: digitalWrite(digit2, DIGIT_ON); break; case 2: digitalWrite(digit3, DIGIT_ON); break; case 3: digitalWrite(digit4, DIGIT_ON); break; } //Now display this letter lightNumber(colorName[digit]); //Turn on the right segments for this letter delayMicroseconds(2000); //Display this digit for a fraction of a second (between 1us and 5000us, 500-2000 is pretty good) //If you set this too long, the display will start to flicker. Set it to 25000 for some fun. //Turn off all segments lightNumber(10); //Turn off all digits digitalWrite(digit1, DIGIT_OFF); digitalWrite(digit2, DIGIT_OFF); digitalWrite(digit3, DIGIT_OFF); } } //Given a number, turns on those segments //If number == 10, then turn off all segments void lightNumber(int numberToDisplay) { #define SEGMENT_ON LOW #define SEGMENT_OFF HIGH /* Segments - A F / / B - G E / / C - D */ switch (numberToDisplay){ case 0: digitalWrite(segA, SEGMENT_ON); digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_ON); digitalWrite(segF, SEGMENT_ON); break; case 1: digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); break; case 2: digitalWrite(segA, SEGMENT_ON); digitalWrite(segB, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 3: digitalWrite(segA, SEGMENT_ON); digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 4: digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segF, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 5: digitalWrite(segA, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segF, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 6: digitalWrite(segA, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_ON); digitalWrite(segF, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 7: digitalWrite(segA, SEGMENT_ON); digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); break; case 8: digitalWrite(segA, SEGMENT_ON); digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_ON); digitalWrite(segF, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 9: digitalWrite(segA, SEGMENT_ON); digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segF, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 10: digitalWrite(segA, SEGMENT_OFF); digitalWrite(segB, SEGMENT_OFF); digitalWrite(segC, SEGMENT_OFF); digitalWrite(segD, SEGMENT_OFF); digitalWrite(segE, SEGMENT_OFF); digitalWrite(segF, SEGMENT_OFF); digitalWrite(segG, SEGMENT_OFF); break; /* Segments - A F / / B - G E / / C - D */ //Letters case 'b': //cdefg digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_ON); digitalWrite(segF, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 'L': //def digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_ON); digitalWrite(segF, SEGMENT_ON); break; case 'u': //cde digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_ON); break; case 'g': //abcdfg digitalWrite(segA, SEGMENT_ON); digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segF, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 'r': //eg digitalWrite(segE, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 'n': //ceg digitalWrite(segC, SEGMENT_ON); digitalWrite(segE, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; //case r case 'e': //adefg digitalWrite(segA, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_ON); digitalWrite(segF, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 'd': //bcdeg digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case ' ': //None digitalWrite(segA, SEGMENT_OFF); digitalWrite(segB, SEGMENT_OFF); digitalWrite(segC, SEGMENT_OFF); digitalWrite(segD, SEGMENT_OFF); digitalWrite(segE, SEGMENT_OFF); digitalWrite(segF, SEGMENT_OFF); digitalWrite(segG, SEGMENT_OFF); break; case 'y': //bcdfg digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segF, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; //case e //case L case 'o': //cdeg digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; } }