I am working with a bread boarded 328P running the 8mhz internal clock boot loader and I have a 32.768khz external crystal. I am trying to put it to sleep and then wake up at predetermined times based on timer2. I have variable "seconds" and use timer2 to increment this variable. So if seconds is greater than variable A then it wake sup and takes pin A0 low and reset seconds to zero and start the cycle over. I am successfully putting it to sleep and waking it up but the cycles are not even and I cant understand why. For example with the posted code it will go to sleep and then wake up every 2 minutes and 14 seconds. It is running very reliably at that interval for the first 3-4 cycles and then the next 3-4 cycles will be all over the place. For example sometimes is it 3 minutes, sometimes 4+ minutes and each cycle is vastly different. Then it will settle down and run reliably at the 2 minutes and 14 seconds for a few cycles. Although it is set for a short duration for testing my goal is to set it for several hours delay. Any suggestions are greatly appreciated!
#include <avr/sleep.h> //Needed for sleep_mode
#include <avr/power.h> //Needed for powering down perihperals such as the ADC/TWI and Timers
volatile long seconds = 0;
int A = 28;
//The very important 32.686kHz interrupt handler
SIGNAL(TIMER2_OVF_vect){
seconds++;
}
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, HIGH); //changed from low to high which was 400ua change
}
//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();
power_timer1_disable();
//power_timer2_disable(); //Needed for asynchronous 32kHz operation
//Setup TIMER2
TCCR2A = 0x00;
TCCR2B = (1<<CS22)|(1<<CS21)|(1<<CS20); //Set CLK/1024 or overflow interrupt every 8s
ASSR = (1<<AS2); //Enable asynchronous operation, 32kHz xtal needed
TIMSK2 = (1<<TOIE2); //Enable the timer 2 interrupt
//Setup external INT1 (pin3) interrupt
EICRA = (1<<ISC11)|(1<<ISC01)|(1<<ISC10)|(1<<ISC00); //Interrupt on rising edge
EIMSK = (1<<INT0)|(1<<INT1); //Enable INT interrupts
//need pull downs if active high, active low can enable internal pulllups
digitalWrite(3, LOW);
digitalWrite(2, LOW);
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
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
//variable 1
if(seconds > A){
reset_5s();
}
}
void reset_5s(){
pinMode(A0, OUTPUT);
digitalWrite(A0, LOW); // needs to be LOW
fake_msdelay(2000);
digitalWrite(A0, HIGH); // needs to be HIGH
pinMode(A0, INPUT);
seconds = 0;
}
//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");
}
}
It's insufficient to declare seconds as volatile, atomic access also needs to be guaranteed. cli() and sei() can be inserted around accesses to the variable, or I would do:
First, why not use the watchdog timer? Second, why not have the switch interrupt on LOW? That lets you wake it up without having to have much running.
I did a post about this sort of stuff:
If you disable brown-out detection you can get current consumption down to 6 uA, per my figures on that page.
In my code I woke up and checked a clock chip for the actual time. You could do something similar, let the watchdog get you within approximately where you want to be, and use the clock chip to get 1-second accuracy.
Been playing with this, pretty interesting. Was off in the weeds a few times, and I'm afraid my code no longer looks much like yours. Found this in the datasheet, I think it's also part of the issue.
• If Timer/Counter2 is used to wake the device up from Power-save or ADC Noise Reduction
mode, precautions must be taken if the user wants to re-enter one of these modes: If reentering
sleep mode within the TOSC1 cycle, the interrupt will immediately occur and the
device wake up again. The result is multiple interrupts and wake-ups within one TOSC1 cycle
from the first interrupt. If the user is in doubt whether the time before re-entering Power-save or
ADC Noise Reduction mode is sufficient, the following algorithm can be used to ensure that
one TOSC1 cycle has elapsed:
a. Write a value to TCCR2x, TCNT2, or OCR2x.
b. Wait until the corresponding Update Busy Flag in ASSR returns to zero.
c. Enter Power-save or ADC Noise Reduction mode.
So at the bottom of loop(), I have:
//Cannot re-enter sleep mode within one TOSC cycle. This provides the needed delay.
OCR2A = 0; //write to OCR2A, we're not using it, but no matter
while (ASSR & _BV(OCR2AUB)) {} //wait for OCR2A to be updated
There's only 1 part might worth checking. Rearrange the Timer 2 configuration steps as in the datasheet page 156.
They advise disabling the timer interrupt > set ASR mode > write new values to the prescaler > enable the interrupt.
Worth a try as they said the configuration might get corrupted if that sequence is not followed.
//Setup external INT1 (pin3) interrupt
EICRA = (1<<ISC11)|(1<<ISC01)|(1<<ISC10)|(1<<ISC00); //Interrupt on rising edge
EIMSK = (1<<INT0)|(1<<INT1); //Enable INT interrupts
//need pull downs if active high, active low can enable internal pulllups
digitalWrite(3, LOW);
digitalWrite(2, LOW);
There is nothing like an internal pull down. So unless you have connected external pull downs this will cause the pins to float. Since these pins can trigger interrupts there is some chance that this is causing erratic wake ups depending on air humidity, the mood of the controller and the current phase of the moon.
@Nick, I disabled the BOD and in sleep mode I measure 1.5uA. I didn't use the watchdog timer because it only last 8 seconds and I need as much as 24 hours. Maybe this can be looped but I didn't know how to do it so I used a variable that increments from timer2. I looked over your example sketch and I did not see anything that would make the WDT timer increment more than 8 seconds. Is it possible to get hours out of it and do you think it would be a better solution than what I have now? Accuracy is a concern and I do need this to maintain a fairly precise clock over a period of years. @Jack, I read all of the millis() articles and spent some hours last night working with it. I tried to implement it but had difficulty since timer0 gets shut off. Since I wont have any other interrupts to worry about is delay() a bad option? I have never heard of atomic.h and have no idea what it does but I will read up on that.
@Udo, I will give that a try and let you know my results. During my testing at this moment you are correct in your assumption that they are floating.
Thanks again everyone, this forum is really amazing!!
No, 8 seconds is its limit, and they say it isn't all that accurate (after all, how accurately do you keep time when you are asleep?).
However, it can be looped, as I did in the post above. So, 10 loops would give you 80 seconds. But for accuracy, I think you would need an external clock (or live with higher power consumption). The solution I did for the earlier poster was to have an onboard clock chip (itself kept alive by a lithium battery that lasts 10 years). Then Atmega sleeps for (say) a minute, powers up the clock board from a digital pin (the clock itself uses 1.5 mA) reads the time, unpowers the clock, and then goes back to sleep.
It is going to be a trade-off. To do it all onboard, accurately, you will need a sleep mode that consumes more power. The external clock solution is still pretty low current. After all, depending on your application, you might just loop the watchdog for 5 minutes, and then check the clock. The other thing you could do is calculate how much watchdog looping you think you need. For example, if you aren't planning to do anything for an hour, you could probably loop hundreds of times, without powering up the clock. Then closer to the target time, you power the clock up more frequently.
I made the change to the interrupt as suggested by Udo and the clock is now running reliably. Thank you very much!
@Nick, I am thinking I will continue to use the 32.768khz crystal which seems to be fairly accurate. I require the Arduino to wake up only a few times per day so the sleep intervals are fairly long so I think I will stick with my existing method of incrementing a variable. I have a GPS connected so I am thinking I can use the time from the NMEA data to update the seconds variable and keep everything running accurately. That is my plan anyway, we will see how it works out....
fbriggs4:
Thanks again everyone, this forum is really amazing!!
The really amazing thing is that I just happened to have a very similar circuit breadboarded, I just had to move an LED to A0 to test it out! Had quite a bit of fun with it.
Some notes:
Turning off many of the individual peripherals (Timers 0 and 1, TWI, SPI, etc.) is not strictly necessary; because Power Save mode stops the associated clocks, they are effectively shut down. So not necessary to replace millis(). Of course it doesn't tick while the MCU is sleeping, but we can still use it to time intervals when not sleeping.
The goToSleep() function also turns off the Brown-Out Detector. I am measuring just about 1µA while the MCU is sleeping.
I took the INT0/INT1 interrupt set-up code out, so it will need to be added back in.
The ATOMIC_BLOCK business is just some covenience macros for manipulating the global interrupt enable bit. From the avr-libc user manual:
The macros in this header file deal with code blocks that are guaranteed to be excuted
Atomically or Non-Atmomically. The term "Atomic" in this context refers to the unability
of the respective code to be interrupted.
These macros operate via automatic manipulation of the Global Interrupt Status (I) bit
of the SREG register. Exit paths from both block types are all managed automatically
without the need for special considerations, i. e. the interrupt status will be restored to
the same value it has been when entering the respective block.
A typical example that requires atomic access is a 16 (or more) bit variable that is
shared between the main execution path and an ISR. While declaring such a variable
as volatile ensures that the compiler will not optimize accesses to it away, it does not
guarantee atomic access to it.
So here's what I ended up with, FWIW:
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/atomic.h>
volatile long ticks; //global variables are initialized to zero (but see below*)
long ticksCopy; //copy of ticks to minimize need for atomic blocks
long maxSeconds = 64; //can be set to any number, but because the Timer2 overflow interrupt is set
//to only occur every 8 seconds, if this is not a modulo-8 number, the LED will
//not blink until the next 8-second interval elapses.
void setup()
{
//To reduce power, set all pins as inputs with pullups
for(int x = 1 ; x < 20 ; x++) { //was x < 18 ... jc
pinMode(x, INPUT);
digitalWrite(x, HIGH);
}
//Set up TIMER2
TIMSK2 = 0; //stop timer2 interrupts while we set up
ASSR = _BV(AS2); //Timer/Counter2 clocked from external crystal
TCCR2A = 0; //override arduino settings, ensure WGM mode 0 (normal mode)
TCCR2B = _BV(CS22) | _BV(CS21) | _BV(CS20); //prescaler clk/1024 -- TCNT2 will overflow once every 8 seconds
TCNT2 = 0; //start the timer at zero
while (ASSR & (_BV(TCN2UB) | _BV(TCR2AUB) | _BV(TCR2BUB))) {} //wait for the registers to be updated
TIFR2 = _BV(OCF2B) | _BV(OCF2A) | _BV(TOV2); //clear the interrupt flags
TIMSK2 = _BV(TOIE2); //enable interrupt on overflow
ATOMIC_BLOCK(ATOMIC_FORCEON) { //Arduino enables interrupts, so RESTORESTATE would work here as well
ticks = 0; //*ensure we start from zero (interrupts may have occurred before we set up the timer)
}
}
void loop()
{
goToSleep();
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
ticksCopy = ticks;
}
if(ticksCopy * 8 >= maxSeconds) {
pinMode(A0, OUTPUT);
digitalWrite(A0, LOW); //turn on the LED for a couple seconds
delay(2000); //LED is wired to Vcc and connected to A0 with a current-limiting resistor
digitalWrite(A0, HIGH);
pinMode(A0, INPUT);
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
ticks = 0;
}
}
}
void goToSleep()
{
byte adcsra, mcucr1, mcucr2;
//Cannot re-enter sleep mode within one TOSC cycle. This provides the needed delay.
OCR2A = 0; //write to OCR2A, we're not using it, but no matter
while (ASSR & _BV(OCR2AUB)) {} //wait for OCR2A to be updated
sleep_enable();
set_sleep_mode(SLEEP_MODE_PWR_SAVE);
adcsra = ADCSRA; //save the ADC Control and Status Register A
ADCSRA = 0; //disable ADC
ATOMIC_BLOCK(ATOMIC_FORCEON) { //ATOMIC_FORCEON ensures interrupts are enabled so we can wake up again
mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE); //turn off the brown-out detector
mcucr2 = mcucr1 & ~_BV(BODSE);
MCUCR = mcucr1; //timed sequence
MCUCR = mcucr2; //BODS stays active for 3 cycles, sleep instruction must be executed while it's active
}
sleep_cpu(); //go to sleep
//wake up here
sleep_disable();
ADCSRA = adcsra; //restore ADCSRA
}
//The Timer/Counter2 Overflow Interrupt Handler
ISR(TIMER2_OVF_vect)
{
ticks++;
}
Jack- I appreciate you sharing your sketch. I read up on the atomic library and as I add functionality to my sketch I think you are right that I will need that.