373 lines
10 KiB
C++
Executable File
373 lines
10 KiB
C++
Executable File
/*
|
|
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 <avr/sleep.h> //Needed for sleep_mode
|
|
#include <avr/power.h> //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<<ADEN); //Disable ADC
|
|
ACSR = (1<<ACD); //Disable the analog comparator
|
|
DIDR0 = 0x3F; //Disable digital input buffers on all ADC0-ADC5 pins
|
|
DIDR1 = (1<<AIN1D)|(1<<AIN0D); //Disable digital input buffer on AIN1/0
|
|
|
|
power_twi_disable();
|
|
power_spi_disable();
|
|
power_usart0_disable();
|
|
power_timer0_disable(); //Needed for delay_ms
|
|
power_timer1_disable();
|
|
//power_timer2_disable(); //Needed for asynchronous 32kHz operation
|
|
|
|
//Setup TIMER2
|
|
TCCR2A = 0x00;
|
|
//TCCR2B = (1<<CS22)|(1<<CS20); //Set CLK/128 or overflow interrupt every 1s
|
|
TCCR2B = (1<<CS22)|(1<<CS21)|(1<<CS20); //Set CLK/1024 or overflow interrupt every 8s
|
|
ASSR = (1<<AS2); //Enable asynchronous operation
|
|
TIMSK2 = (1<<TOIE2); //Enable the timer 2 interrupt
|
|
|
|
//Setup external INT0 interrupt
|
|
EICRA = (1<<ISC01); //Interrupt on falling edge
|
|
EIMSK = (1<<INT0); //Enable INT0 interrupt
|
|
|
|
//System clock futzing
|
|
//CLKPR = (1<<CLKPCE); //Enable clock writing
|
|
//CLKPR = (1<<CLKPS3); //Divid the system clock by 256
|
|
|
|
//Serial.begin(9600);
|
|
//Serial.println("32kHz Testing:");
|
|
|
|
sei(); //Enable global interrupts
|
|
}
|
|
|
|
void loop() {
|
|
sleep_mode(); //Stop everything and go to sleep. Wake up if the Timer2 buffer overflows or if you hit the button
|
|
|
|
if(show_the_time == TRUE) {
|
|
|
|
//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
|
|
|
|
//Here is where we assume 12 hour display
|
|
while(hours > 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;
|
|
}
|
|
}
|
|
|