Hello,
I'm playing around with Timer2 in asynchronous mode on a custom board. External clock source is a DS3231 32kHz output (with 4k7 pull-up resistor) connected to TOSC1 pin. The sketch is only a test program at this point, so it doesn't do anything specific, just trying to flash a led with sleep mode and wake up by Timer2 interrupt. I've tried to set related registers cautiously (e.g. waiting for ASSR busy bits to be cleared if necessary) and also added comments based on the datasheet.
My problem is that the uC doesn't wake up, not even once, unless I add a little delay before goToSleep()
function (leds stay in HIGH state).
I simply don't get it. The only thing that would explain this, is Timer2 interrupt happening somewhere between enableTimer2Interrupt()
and goToSleep()
(and related Timer2 interrupt is disabled by the related routine), but why does delay(1)
help?
(32kHz is enabled in every case, I've checked it with a scope)
/******************************************************************************/
/* INCLUDE, DEFINE */
/******************************************************************************/
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <DS3232RTC.h>
#include <Wire.h>
/******************************************************************************/
/* CONSTANTS */
/******************************************************************************/
const uint8_t BLUE_LED_PIN = 6;
const uint8_t RED_LED_PIN = 7;
/*RTC Status Register bits*/
/*********************************************|
| OSF | X | X | X | EN32kHz | BSY | A2F | A1F |
|*********************************************/
const uint8_t RTC_EN32KHZ = 3;
const uint8_t RTC_CONTROL_ADDRESS = 0x0E;
const uint8_t RTC_STATUS_ADDRESS = 0x0F;
/******************************************************************************/
/* VARIABLES, OBJECTS */
/******************************************************************************/
DS3232RTC myRTC;
/******************************************************************************/
/* SETUP */
/******************************************************************************/
void setup()
{
setPinModes();
/*set default for RTC*/
initRTC();
/*set asynchronous operation of Timer2 and do a basic*/
/* timer, sleep function check with led flashing*/
setAsynchronousTimer2();
}
/******************************************************************************/
/* LOOP */
/******************************************************************************/
void loop()
{
digitalWrite(BLUE_LED_PIN, HIGH);
digitalWrite(RED_LED_PIN, HIGH);
enable32kHz();
// (3 + 1) * 31.25ms = 125ms
setTimer2TopValue(3);
resetTimer2();
enableTimer2Interrupt();
// this delay helps
// delay(1ul);
goToSleep();
disable32kHz();
digitalWrite(BLUE_LED_PIN, LOW);
digitalWrite(RED_LED_PIN, LOW);
delay(125ul);
}
/******************************************************************************/
/* RTC */
/******************************************************************************/
void initRTC()
{
Wire.begin();
myRTC.writeRTC(RTC_STATUS_ADDRESS, 0x00);
myRTC.writeRTC(RTC_CONTROL_ADDRESS, 0x00);
}
void enable32kHz()
{
myRTC.writeRTC(RTC_STATUS_ADDRESS, bit(RTC_EN32KHZ));
/*make sure that square-wave is stabilized*/
delay(2ul);
}
void disable32kHz()
{
/*uC can be right after sleep, wait a little*/
delay(1ul);
myRTC.writeRTC(RTC_STATUS_ADDRESS, 0x00);
}
/******************************************************************************/
/* SLEEP */
/******************************************************************************/
void goToSleep()
{
uint8_t old_ADCSRA = 0;
/*turn off I2C (twi_disable)*/
Wire.end();
/*disable ADC*/
old_ADCSRA = ADCSRA;
ADCSRA = 0;
/*do not interrupt before going to sleep or the ISR will detach*/
/*interrupts and wake by interrupt will be disabled*/
noInterrupts();
/*disable watchdog*/
wdt_disable();
/*clear various "reset" flags*/
MCUSR = 0;
/*full power-down doesn't respond to Timer2, use Power-save mode*/
set_sleep_mode(SLEEP_MODE_PWR_SAVE);
sleep_enable();
/*clear interrupt flags by writing logical one*/
EIFR = bit(INTF0);
EIFR = bit(INTF1);
/*turn off brown-out enable in software*/
/*to disable BOD in relevant sleep modes, both BODS and BODSE must first be set to 1*/
/*then, to set the BODS bit, BODS must be set to 1 and BODSE must be set to 0 within 4 clock cycles*/
MCUCR = bit(BODS) | bit(BODSE);
MCUCR = bit(BODS);
/*it is guaranteed that the sleep_cpu call will be done as the processor*/
/*executes the next instruction after interrupts are turned on*/
/*one cycle*/
interrupts();
/*one cycle*/
sleep_cpu();
/*AFTER ISR execution, running is continue from here*/
ADCSRA = old_ADCSRA;
/*i2c is needed every time after wakeup*/
Wire.begin();
}
/******************************************************************************/
/* INTERRUPT ROUTINES */
/******************************************************************************/
ISR(TIMER2_COMPA_vect)
{
/*disable Timer2 interrupt*/
TIMSK2 = 0;
}
/******************************************************************************/
/* TIMER2 RELATED */
/******************************************************************************/
void setAsynchronousTimer2()
{
/*!! Warning: When switching between asynchronous and synchronous clocking !!*/
/*!! of Timer/Counter2, the Timer Registers TCNT2, OCR2x, and TCCR2x might !!*/
/*!! be corrupted. A safe procedure for switching clock source is: !!*/
/*!! a. Disable the Timer/Counter2 interrupts by clearing OCIE2x and TOIE2. !!*/
/*!! b. Select clock source by setting AS2 as appropriate. !!*/
/*!! c. Write new values to TCNT2, OCR2x, and TCCR2x. !!*/
/*!! d. To switch to asynchronous operation: Wait for TCN2xUB, OCR2xUB, and TCR2xUB. !!*/
/*!! e. Clear the Timer/Counter2 interrupt flags. !!*/
/*!! f. Enable interrupts, if needed. !!*/
/*disable interrupts (by clearing OCIE2x and TOIE2)*/
TIMSK2 = 0;
/*clean start for control registers, stop and reset Timer2*/
TCCR2A = 0;
TCCR2B = 0;
TCNT2 = 0;
/*Writing to EXCLK should be done before asynchronous operation is selected*/
ASSR = bit(EXCLK);
/*select asynchronous operation of Timer2*/
ASSR |= bit(AS2);
/*!! When (after) the value of AS2 is changed, the contents of TCNT2, OCR2A, !!*/
/*!! OCR2B, TCCR2A and TCCR2B might be corrupted. Each of the five mentioned !!*/
/*!! registers have their individual temporary register, which means that !!*/
/*!! e.g. writing to TCNT2 does not disturb an OCR2x write in progress !!*/
/*!! If a write is performed to any of the five Timer/Counter2 !!*/
/*!! while its update busy flag is set, the updated value might !!*/
/*!! get corrupted and cause an unintentional interrupt to occur. !!*/
/*wait until TCCR2A and TCCR2B are ready to be updated with a new value*/
/*(related busy bits are cleared by hardware)*/
while ((ASSR & (bit(TCR2AUB) | bit(TCR2BUB))) == 1) continue;
/*set CTC mode and prescaler to 1024 (1024*(1/32768) = 31,25ms)*/
TCCR2A = bit(WGM21);
TCCR2B = bit(CS22) | bit(CS21) | bit(CS20);
/*wait until TCCR2A and TCCR2B is updated from their temporary*/
/*storage register (related busy bits are cleared by hardware)*/
while ((ASSR & (bit(TCR2AUB) | bit(TCR2BUB))) == 1) continue;
}
void setTimer2TopValue(uint8_t topValue)
{
/*wait until OCR2A is ready to be updated with a new value*/
while ((ASSR & bit(OCR2AUB)) == 1) continue;
/*e.g. 5000ms / 31,25ms = 160 --> count to 159 (zero-relative)*/
OCR2A = topValue;
/*wait until OCR2A is updated from temporary storage register*/
while ((ASSR & bit(OCR2AUB)) == 1) continue;
}
void resetTimer2()
{
/*!! 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 !!*/
/*reset timer*/
TCNT2 = 0;
/*wait until TCNT2 is updated from temporary storage register*/
while ((ASSR & bit(TCN2UB)) == 1) continue;
}
void enableTimer2Interrupt()
{
noInterrupts();
/*enable Timer2 compare match A interrupt*/
TIMSK2 = bit(OCIE2A);
/*clear (pending) interrupt-flags (flags are cleared by writing a logic one)*/
TIFR2 = bit(OCF2B) | bit(OCF2A) | bit(TOV2);
interrupts();
}
void setPinModes()
{
analogReference(DEFAULT);
pinMode(BLUE_LED_PIN, OUTPUT);
digitalWrite(BLUE_LED_PIN, LOW);
pinMode(RED_LED_PIN, OUTPUT);
digitalWrite(RED_LED_PIN, LOW);
}
Anyone have any ideas?
Thanks in advance.