/* 6-13-2011 Low Power Testing 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 code is written to control a 7 segment display with as little power as possible, waking up only when the external button is hit or when the 32kHz oscillator rolls over every 8 seconds. Make sure to turn off Brown out detect (uses ~16uA). 6-23-2011: Down to 38uA! 6-29-2011: 17uA, not sure why. 7-2-2011: Currently (hah!) 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 Let's reduce the system clock to 8MHz/256 so that when servicing Timer2 int, we run even lower We are now at ~1.05uA on average */ #include //Needed for sleep_mode #include //Needed for powering down perihperals such as the ADC/TWI and Timers #define TRUE 1 #define FALSE 0 int show_the_time = FALSE; //For testing long seconds = 2317; int minutes = 58; int hours = 12; int digit1 = 11; //PWM, Display pin 1 int digit2 = 10; //PWM, Display pin 2 int digit3 = 9; //PWM, Display pin 6 int digit4 = 6; //PWM, Display pin 8 int segA = A1; //Display pin 14 int segB = 3; //Display pin 16 int segC = 4; //Display pin 13 int segD = 5; //Display pin 3 int segE = A0; //Display pin 5 int segF = 7; //Display pin 11 int segG = 8; //Display pin 15 //The very important 32.686kHz interrupt handler SIGNAL(TIMER2_OVF_vect){ //seconds++; seconds += 8; //We sleep for 8 seconds instead of 1 to save more power //We don't update minutes and hours here to save power //Instead, we update the minutes and hours when you hit the display button } //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(2, INPUT); //This is the main button, tied to INT0 digitalWrite(2, 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); //These are PWM pins pinMode(digit1, OUTPUT); pinMode(digit2, OUTPUT); pinMode(digit3, OUTPUT); pinMode(digit4, 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; //while(hours > 24) hours -= 24; /*Serial.print(hours, DEC); Serial.print(":"); Serial.print(minutes, DEC); Serial.print(":"); Serial.println(seconds, DEC);*/ for(int x = 0 ; x < 500 ; x++) displayNumber(1234); digitalWrite(A5, HIGH); fake_msdelay(100); //Small debounce digitalWrite(A5, LOW); show_the_time = FALSE; } } //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 a number, we display 10:22 //After running through the 4 numbers, the display is left turned off void displayNumber(int toDisplay) { #define DISPLAY_BRIGHTNESS 500 //Display brightness //Each digit is on for a certain amount of microseconds //Then it is off until we have reached a total of 20ms for the function call //Let's assume each digit is on for 1000us //If each digit is on for 1ms, there are 4 digits, so the display is off for 16ms. //That's a ratio of 1ms to 16ms or 6.25% on time (PWM). //Let's define a variable called brightness that varies from: //5000 blindingly bright (15.7mA current draw per digit) //2000 shockingly bright (11.4mA current draw per digit) //1000 pretty bright (5.9mA) //500 normal (3mA) //200 dim but readable (1.4mA) //50 dim but readable (0.56mA) //5 dim but readable (0.31mA) //1 dim but readable in dark (0.28mA) #define DIGIT_ON HIGH #define DIGIT_OFF LOW //long beginTime = millis(); 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); break; case 3: digitalWrite(digit3, DIGIT_ON); break; case 4: digitalWrite(digit4, DIGIT_ON); break; } //Turn on the right segments for this digit lightNumber(toDisplay % 10); toDisplay /= 10; //delayMicroseconds(DISPLAY_BRIGHTNESS); //Display this digit for a fraction of a second (between 1us and 5000us, 500 is pretty good) fake_usdelay(1500); //Display this digit for a fraction of a second (between 1us and 5000us, 500 is pretty good) //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); } //while( (millis() - beginTime) < 10) ; //Wait for 20ms to pass before we paint the display again fake_msdelay(1); } //Given a number, turns on those segments //If number == 10, then turn off number void lightNumber(int numberToDisplay) { #define SEGMENT_ON LOW #define SEGMENT_OFF HIGH 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); digitalWrite(segG, SEGMENT_OFF); break; case 1: digitalWrite(segA, SEGMENT_OFF); digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_OFF); digitalWrite(segE, SEGMENT_OFF); digitalWrite(segF, SEGMENT_OFF); digitalWrite(segG, SEGMENT_OFF); break; case 2: digitalWrite(segA, SEGMENT_ON); digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_OFF); digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_ON); digitalWrite(segF, SEGMENT_OFF); digitalWrite(segG, SEGMENT_ON); break; case 3: digitalWrite(segA, SEGMENT_ON); digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_OFF); digitalWrite(segF, SEGMENT_OFF); digitalWrite(segG, SEGMENT_ON); break; case 4: digitalWrite(segA, SEGMENT_OFF); digitalWrite(segB, SEGMENT_ON); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_OFF); digitalWrite(segE, SEGMENT_OFF); digitalWrite(segF, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 5: digitalWrite(segA, SEGMENT_ON); digitalWrite(segB, SEGMENT_OFF); digitalWrite(segC, SEGMENT_ON); digitalWrite(segD, SEGMENT_ON); digitalWrite(segE, SEGMENT_OFF); digitalWrite(segF, SEGMENT_ON); digitalWrite(segG, SEGMENT_ON); break; case 6: digitalWrite(segA, SEGMENT_ON); digitalWrite(segB, SEGMENT_OFF); 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); digitalWrite(segD, SEGMENT_OFF); digitalWrite(segE, SEGMENT_OFF); digitalWrite(segF, SEGMENT_OFF); digitalWrite(segG, SEGMENT_OFF); 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(segE, SEGMENT_OFF); 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; } }