How to sleep AVR from within an interrupt handler?

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi - I have an ATMEGA168PV that I would like to put in to power save mode quite frequently. I'm using AVR Studio 5.0 and using AVR GCC.

The idea is to have the timer2 interrupt periodically wake up the sleeping AVR and then put the AVR back into power save mode. It will just repeat in this loop for quite some time.
So, at the end of my timer2 interrupt handler, I have this code:

	
SMCR = (1<<SM1) | (1<<SM0) | (1<<SE);
sleep_cpu();

Which I believe will put the AVR into power save mode.

Is that all I have to do?

I think that after it executes this code, the AVR will go to sleep, wait for the timer2 interrupt to fire, and then start from the beginning of the timer2 interrupt handler. But this line sort of sketches me out:

Quote:
If an enabled interrupt occurs while the MCU is in a sleep mode, the MCU wakes up. The MCU
is then halted for four cycles in addition to the start-up time, executes the interrupt routine, and
resumes execution from the instruction following SLEEP

So the way I read that is that it won't really affect me at all since I will be sleeping before exiting the interrupt routine. However, once I'm ready to stop sleeping (which will happen), I finally will exit the timer2 interrupt routine (instead of sleeping before I exit), and then, judging from the quote above, I'll jump right back into the interrupt routine (starting just below the sleep_cpu(); line)

Does this make sense?

Thanks!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I think that after it executes this code, the AVR will go to sleep, wait for the timer2 interrupt to fire, and then start from the beginning of the timer2 interrupt handler.
But you are already inside the interrupt, so the global interrupt flag is cleared. And if you set it, the interrupt will be called, but it will be nested within the current interrupt, so you will soon run out of stack space.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Koshchi wrote:
Quote:
I think that after it executes this code, the AVR will go to sleep, wait for the timer2 interrupt to fire, and then start from the beginning of the timer2 interrupt handler.
But you are already inside the interrupt, so the global interrupt flag is cleared. And if you set it, the interrupt will be called, but it will be nested within the current interrupt, so you will soon run out of stack space.

Yeah - I had wondered about that.

But I would think that the way I want to handle this (as in, go to sleep from within the interrupt handler) is quite standard. So what is the appropriate way of going about this?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

But I would think that the way I want to handle this (as in, go to sleep from within the interrupt handler) is quite standard.

No.
Quote:

So what is the appropriate way of going about this?

Don't go to sleep in the ISR.

I can only give >>my<< "standard"/"appropriate" way of doing it.

-- In general, the wakeup ISRs are empty or nearly so. Again in general, they don't even set a flag.
-- In the mainline where the sleep is invoked, getting to the next instruction is an indication of a wakeup (or a race going to sleep where sleep was cancelled at the last moment).
-- So you have awoken for some reason. You yawn and stretch and look around to see what has awakened you. If it was a dream, you again prepare for sleep and doze off again. If it is a burglar, then you start firing until the threat is resolved. (In other words, if an input to act on fires--you act on it.

It makes sense, anyhow, 'cause in a real app you are probably going to sleep to save power. Thus, preparing for sleep involves shutting down subsystems and tweaking the PRR register. And the reverse upon wakeup.

Lee

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
But I would think that the way I want to handle this (as in, go to sleep from within the interrupt handler) is quite standard.

I might be a little dumb, but to me it is very counter-intuitive to do it that way Michael. As far as interrupts are concerned, keep them short, keep them fast & keep them simple!
Perhaps so that we dont confuse Lee & Lee , just refer to me as LEE, as in Lee & LEE :wink:

Charles Darwin, Lord Kelvin & Murphy are always lurking about!
Lee -.-
Riddle me this...How did the serpent move around before the fall?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK so to answer both the LeEs:

My code, as is, is entirely in my ISR. Code is running a nixie clock. there is a while (1) ; loop doing nothing in main. All code is in or called by the timer2 ISR. That includes checking buttons, increasing time, sending time to nixie driver, updating PWM duty cycle for boost supply, etc.

I know people talk a lot about needing to have short ISRs. But my understanding is that that is typically to avoid overlap of ISRs and that sort of thing. In this device, that is a non issue - all actions in the ISR take a known amount of time. I have ensured that the timer2 ISR will never take so long that it misses the next timer2 interrupt. And the timer2 interrupt is the only interrupt enabled in this system.

I could move all my code into main() and just have the ISR write a flag that tells the main code to do its thing - but that seems a bit silly and pointless to me unless I'm missing something?

Either way - I would rather not make any major changes to my code (considering that it's about 1K lines and is already written and working). Best idea I'm having is something like this:

Have a global variable called SleepNow; If I want to sleep, at the end of my timer2 ISR write SleepNow = 1;

in my main, have a constant loop like this going:

while (1){
if (SleepNow){
SleepNow = 0;
SMCR = (1<<SM1) | (1<<SM0) | (1<<SE);
sleep_cpu(); 
}
}

Does that seem like a valid solution?

Thanks!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

My code, as is, is entirely in my ISR. Code is running a nixie clock. there is a while (1) ; loop doing nothing in main. All code is in or called by the timer2 ISR. That includes checking buttons, increasing time, sending time to nixie driver, updating PWM duty cycle for boost supply, etc.

So, if >>you<< choose to do things in an uncommon way, then why are you decrying the "normal" behaviour of AVR sleep+interrupts?
Quote:

But my understanding is that that is typically to avoid overlap of ISRs and that sort of thing.

Huh? What is "overlap of ISRs and that sort of thing"?

Quote:

unless I'm missing something?

Yes, Have the main loop go to sleep, every time. Done. (Well, not really--in a real app, as I mentioned, there is preparing for sleep and ablutions after wakeup.)

If your app is trivial in the usual sense of the word--one interrupt source, no real main loop/background code, no comms, etc.--then what does it matter whether you go to sleep or not?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Either way - I would rather not make any major changes to my code
If your entire code is really in this one ISR, then it would not be a major change at all. Simply cut the code from the ISR and paste it into the empty while(1) loop in main().

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
Quote:

My code, as is, is entirely in my ISR. Code is running a nixie clock. there is a while (1) ; loop doing nothing in main. All code is in or called by the timer2 ISR. That includes checking buttons, increasing time, sending time to nixie driver, updating PWM duty cycle for boost supply, etc.

So, if >>you<< choose to do things in an uncommon way, then why are you decrying the "normal" behaviour of AVR sleep+interrupts?

I did not intend to decry anything. Sorry if it came off that way.
Quote:
Quote:

But my understanding is that that is typically to avoid overlap of ISRs and that sort of thing.

Huh? What is "overlap of ISRs and that sort of thing"?

I meant that you want short ISRs so that you can catch all interrupts - if you have serial data coming in at 250000 baud but your RX interrupt handler takes 5ms every time it runs... you would need your interrupt handlers to overlap (which isn't possible) to catch all the data.

Quote:

Quote:

unless I'm missing something?

Yes, Have the main loop go to sleep, every time. Done. (Well, not really--in a real app, as I mentioned, there is preparing for sleep and ablutions after wakeup.)

If your app is trivial in the usual sense of the word--one interrupt source, no real main loop/background code, no comms, etc.--then what does it matter whether you go to sleep or not?


This is a fair point. I had been under the assumption that there was a decent amount of start up time associated with the internal oscillator (which is my main clock source, though an external 32.768KHz crystal is my timer2 clock source). Looks like it's only 6 clock cycles? (I'm assuming that's what 6CK means in table 9-12 in the datasheet). That's great. That's really fast and means that I can probably go to sleep in any mode (I interrupt at 16Hz in battery mode, 1024Hz in non battery mode).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Either way - I would rather not make any major changes to my code (considering that it's about 1K lines and is already written and working).

The point that it was already working was not clear in the first post, especially when it included the following statement of intent rather than completed.
Quote:
I would like to put in to power save mode quite frequently.

Quote:
Does this make sense?

Yes, I suppose it does, but I would suggest that it does not make common sense! :wink:
On the basis that others may read this post (especially 12 yo newbies) and start modeling their code on your method and then try and implement their code with several interrupts, it is incumbent to point out that it is not good professional practice. It will probably bite you in the arse when you try to extend/modify/maintain your code one day. :wink:

Charles Darwin, Lord Kelvin & Murphy are always lurking about!
Lee -.-
Riddle me this...How did the serpent move around before the fall?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Set a flag in your isr when you want to sleep. Main line code tests the flag, if set, puts AVR to sleep. Timer wakes AVR up, does some work, sets flag,returns and main line code puts it to sleep.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What Kartman said.

It is possible to sleep inside one ISR if another interrupt is used for wake, and you ensure the sleep ISR does no recursion. One time that might be useful is for precise timing of an interrupt chain that varies between wake and sleep cycles (e.g. wireless strobe reception).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This ISR works for me to go sleeping if power is lost.
INT1 sense if power pre regulators is lost and MCU goes to sleep inside INT1 ISR, to periodically wake up by TCNT2 Overflow ISR.

;******************** INT1 ISR *************************

;This ISR executes when power to board is missing.
;Program execution will remain inside INT1 ISR until
;power return to board.
;Program will then force a Watchdog reset.

;INT1 is disabled to enable RTC ISR within INT1 ISR.
;Global interrupt is re-enabled to allow RTC ISR.
;Ports are set to low outputs with exception
;for PWR pin(INT1) which is input to sense when
;power come back on again.
;ADC is disabled to save energy
;Watchdog is disabled to save energy.

;Program then enters a sleep sequence loop.
;Sleep is enabled in POWER SAVE MODE. 
;This mode allow TCNT2 to run thus keeping 
;date and time.
;Both BASEUNIT and XPAND UNIT wake up by RTC ISR.
;Both units test PWR pin and loop sleep sequence
;while PWR pin is low input.
;Since only BASEUNIT is keeping date and time
;XPAND UNIT will skip time ticking routine.

;If PWR pin input is high again current
;date and time are saved in EEPROM.
;Watchdog is enabled with shortest timeout.(16mS)..
;This is done to enable a Watchdog reset and
;initialize both units as if PWR reset occured.
;Only difference is that in Watchdog reset
;BASEUNIT will retain date and time saved from EEPROM.

;Registers used: ZERO * TEMP * DATA

;*******************************************************

enter_sleep:
	out	eimsk,zero		;disable INT1 ISR
	sts	adcsra,zero		;disable ADC
	ldi	temp,0xff		;all Port pins pull-up
	out	ddra,temp		;PortA Inputs
	out	porta,zero		;PortA Pull_ups
	out	ddrb,temp		;PortB Inputs
	out	portb,zero		;PortB Pull-ups
	ldi	temp,0xfb		;INT1 no Pull-up
	out	ddrd,temp		;PortD Inputs
	out	portd,zero		;PortD Pull-ups
	lds	data,gsm_state	;load GSM STATE
	sbrs	data,0		;if OFF LINE
	rjmp	no_phone		;skip PWR SMS
send_pwr_sms:
	ldi	zl,28			;"POWER" PTR
	ldi	qh,0			;flag = ALARM NOT DONE
	call	missing_alarm	;PWR SMS to TEXT BUFFER	
	call	sms_send_slot	;SEND PWR SMS
no_phone:
	ldi	temp,0xff		;all Port pins pull-up
	out	ddrc,temp		;PortC Inputs
	out	portc,zero		;PortC Pull-ups
	lds	data,soft_sys	;load SYS MODE
dog_disable:
	wdr				;reset WATCHDOG
	ldi	temp,0x18		;set WDCE + WDE
	sts	wdtcsr,temp		;save WDTCSR
	ldi	temp,0x00		;disable WATCHDOG
	sts	wdtcsr,temp		;save WDTCSR
	ldi	temp,0x80		;enable
	sts	clkpr,temp		;CLOCK PRESCALE CHANGE
	ldi	temp,0x08		;divide CPU CLK/256
	sts	clkpr,temp		;save change
	sei				;enable nested ISR
sleep_sequence:
	ldi	temp,0x07		;enable SLEEP in
	out	smcr,temp		;POWER SAVE MODE
	sleep				;enter POWER SAVE MODE
awakened:
	out	smcr,zero		;disable POWER SAVE MODE
	sbic	pind,pwr		;if PWR ON
	rjmp	pwr_back		;exit SLEEP MODE
	sts	tcnt2,zero		;else reset TCNT2
wait_awake:
	lds	temp,assr		;load ASSR
	sbrc	temp,4		;if TIMER2 BUSY		
	rjmp	wait_awake		;try again
	sbrc	data,0		;if XPAND UNIT skip RTC 
	call	real_time		;else tick REAL TIME CLOCK
	rjmp	sleep_sequence	;wait for next RTC TICK

pwr_back:
	ldi	temp,0x80		;enable
	sts	clkpr,temp		;CLOCK PRESCALE CHANGE
	ldi	temp,0x08		;CPU CLK no prescale
	sts	clkpr,temp		;save change
	sbic	pinc,sys_mode	;if SYS MODE = XPAND
	rjmp	dog_reset		;skip RTC
	ldi	xh,0x00		;else msb EEPROM
	ldi	xl,low(rtc_day)+16;lsb EEPROM
	mov	data,day		;copy DAY
	rcall	prom_write		;save to EEPROM
	ldi	xl,low(rtc_hour)+16;lsb EEPROM
	mov	data,hour		;copy HOUR
	rcall	prom_write		;save to EEPROM
	ldi	xl,low(rtc_min)+16;lsb EEPROM
	mov	data,min		;copy MINUTE
	rcall	prom_write		;save to EEPROM
	ldi	xl,low(test_0)+16	;lsb EEPROM
	mov	data,tick		;copy TICK
	rcall	prom_write		;save to EEPROM
dog_reset:
	wdr				;reset WATCHDOG
	lds	temp,wdtcsr		;load WDTCSR
	ldi	temp,0x18		;set WDE + WDCE
	sts	wdtcsr,temp		;save WDTCSR
	ldi	temp,0x08		;set WDE + RESET @16mS
	sts	wdtcsr,temp		;save WDTCSR
do_dog_reset:
	rjmp	do_dog_reset	;do WATCHDOG RESET


TCNT2 ISR using external 32,768KHz crystal is simply

.org	ovf2addr
	inc	rtc			;tick RTC
	reti				;return ISR