Memory allocation inside ISR

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

Hi,

I was just wondering, what is efficient?

- declaring a temp variable inside ISR.
or
- declaring it as a global variable and use as temp variable in ISR.

Thanks,
K

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

Define "efficient".

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

Execution speed?
Code space?
Source size?

BTW, if you declare globally, you would still need VOLATILE attribute.

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

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

Quote:
BTW, if you declare globally, you would still need VOLATILE attribute.

Not necessarily. Note that the OP wrote
Quote:
use as temp variable in ISR

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

How wide is the variable?

"How long is a piece of string?"

If you are cycle-counting trying to get a 24-cycle ISR down to 20 cycles, then (IME) the "most efficient" in terms of cycle count is to have dedicated registers to use when in the ISR. That may not be practical for your toolchain.

If not cycle-counting, then I'd say that the size and cycle count would be about the same for the global (or static) variables versus locals. That would be for, say, about 4 bytes worth.

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

Why not try yourself and see in the asm listing/disassembly?

Locals need some way to allocate space for them on the stack, so there will be some extra handling in prologue/epilogue.

See also Lee's comment on register allocation. This path is not trivial and laid with gotchas. But if you are that concerned about efficiency, you are bound to go for assembler - not [only] for speed, but mainly for control.

JW

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

Thank you all for sharing your points.

I am using an ATmega32.
I have all three timers used, 1. timer1 for PWM for two motors timer0 for Servo PWM, timer2 for PID sampling.

timer2 has a overflow interrupt at 72 clicks/sec rate. This ISR reads the encoder value into a temp variable (an int8_t) and then pass that along PID function for the calculations (right now I am having the temp variable as global so I do not pass it as parameter to PID function).
Then there are set of lines to construct a string in a tempstringbuffer[40] that send to PC serial port via (interrupt based UART). This tempstringbuffer is defined global as well.

I just had a thought that keeping them global will permanently consume those many bytes from the RAM.
but if I put them as local to ISR they will be allocated, used and then free the RAM once the ISR is finished, and make it available for other variables.
But then this ISR is called 72 times a second, so the allocation and freeing happen so many times, and consume so many CPU cycles.

Thus I wonder what would be my preferred way.

wek - I am not very comfortable with asm yet, never tried it before. So if I take your words, I should be fine with globals.

JohanEkdahl - You prefer OP's way (I am sure I do not know OP). i.e. use local variables.

ka7ehk - I think I am concerned about speed of execution. My code is approx. 9 KB of the 32KB limit on ATMEGA32 so code space and source size are no worries as of now.

Thanks
K

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

Quote:

I just had a thought that keeping them global will permanently consume those many bytes from the RAM.
but if I put them as local to ISR they will be allocated, used and then free the RAM once the ISR is finished, and make it available for other variables.

Just sit back and think about that for a few moments.

Remember that an interrupt can occur at any time in your code.

It could happen to be executing in the most deeply nested function with the highest use of stack frame automatics.

So what happens if the interrupt fires and then tries to make room for some more (ISR owned) automatics and there is not enough RAM to do so?

Disaster - that's what.

So whether the variables themselves are dynamically created on the stack or permanently allocated in .bss/.data doesn't matter because you have to ensure that there's always room for their allocation no matter what.

In which case you might as well allocate them globally because that way you are guaranteed the ISR will have room for all the variables it uses.

HOWEVER if your ISR() just has a few ints/chars/uint8/16's defined at the top there about a 90-100% chance that they'll never be allocated in RAM anyway but will be optimised into some of the 32 machine registers. This cannot happen if they are global.

So I'd suggest you keep anything local to the ISR() that is "small" as stack frame automatics and anything "large" (buffers/arrays) global to guarantee there will be RAM to accommodate them at any time (as there has to be).

Quote:
so the allocation and freeing happen so many times, and consume so many CPU cycles.

How many cycles exactly? Have you analysed the code? The ISR() is going to be reading SP (to Y for the frame pointer) anyway so the only difference is an SBIW(*) that adjusts it down to create the frame space. That is 2 additional cycles 72 times a second. So 144 cycles in a second. In that second an 8MHz (say) CPU has executed 8,000,0000 cycles. Do 144 matter?

BTW you said:

Quote:

Then there are set of lines to construct a string in a tempstringbuffer[40] that send to PC serial port via (interrupt based UART).

You mean you are doing that in the ISR()? Why on earth would you do that? Surely the transmission of this string can wait a few milliseconds until control is back to the main() loop and hits the code that acts on a volatile flag that has been set?

(*) this shows the difference in an ISR() that uses a stack frame and one that doesn't:

#include 
#include 

#define STACK
#ifndef STACK
char n;
char buff[10];
#endif 

ISR(INT0_vect) {
#ifdef STACK
  char n;
  char buff[10];
#endif
  for (n=0; n<10; n++) {
	  buff[n] = PINB;
  }
  PORTD = buff[3];
  PORTD = buff[7];
}

int main(void)
{
	while(1) {
	}
}

With STACK defined:

__vector_1:
//==> ISR(INT0_vect) {
	push r1
	push r0
	in r0,__SREG__
	push r0
	clr __zero_reg__
	push r18
	push r24
	push r25
	push r30
	push r31
	push r28
	push r29
	in r28,__SP_L__
	in r29,__SP_H__
	sbiw r28,10
	out __SP_H__,r29
	out __SP_L__,r28
/* prologue: Signal */
/* frame size = 10 */
/* stack size = 20 */
.L__stack_usage = 20
	movw r30,r28
	adiw r30,1
//==> ISR(INT0_vect) {
	movw r24,r28
	adiw r24,11
.L2:
//==> 	  buff[n] = PINB;
	in r18,0x16
	st Z+,r18
//==>   for (n=0; n<10; n++) {
	cp r30,r24
	cpc r31,r25
	brne .L2
//==>   PORTD = buff[3];
	ldd r24,Y+4
	out 0x12,r24
//==>   PORTD = buff[7];
	ldd r24,Y+8
	out 0x12,r24
/* epilogue start */
//==> }
	adiw r28,10
	in __tmp_reg__,__SREG__
	cli
	out __SP_H__,r29
	out __SREG__,__tmp_reg__
	out __SP_L__,r28
	pop r29
	pop r28
	pop r31
	pop r30
	pop r25
	pop r24
	pop r18
	pop r0
	out __SREG__,r0
	pop r0
	pop r1
	reti

and without STACK defined:

__vector_1:
//==> ISR(INT0_vect) {
	push r1
	push r0
	in r0,__SREG__
	push r0
	clr __zero_reg__
	push r24
	push r25
	push r30
	push r31
/* prologue: Signal */
/* frame size = 0 */
/* stack size = 7 */
.L__stack_usage = 7
//==>   for (n=0; n<10; n++) {
	sts n,__zero_reg__
	rjmp .L2
.L3:
//==> 	  buff[n] = PINB;
	in r25,0x16
	mov r30,r24
	ldi r31,0
	subi r30,lo8(-(buff))
	sbci r31,hi8(-(buff))
	st Z,r25
//==>   for (n=0; n<10; n++) {
	subi r24,lo8(-(1))
	sts n,r24
.L2:
//==>   for (n=0; n<10; n++) {
	lds r24,n
	cpi r24,lo8(10)
	brlo .L3
//==>   PORTD = buff[3];
	lds r24,buff+3
	out 0x12,r24
//==>   PORTD = buff[7];
	lds r24,buff+7
	out 0x12,r24
/* epilogue start */
//==> }
	pop r31
	pop r30
	pop r25
	pop r24
	pop r0
	out __SREG__,r0
	pop r0
	pop r1
	reti

Most of the difference is to do with how the variables are accessed. Not how they are created/destroyed.

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

clawson wrote:

Most of the difference is to do with how the variables are accessed. Not how they are created/destroyed.

Thanks Clawson, it makes a lot more sense now (I learnt MCUs by trail and error and you know the theory in and out, that makes the difference, probably I should start reading from A to Z rather than picking up topics).

Regards,
K

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

clawson wrote:
HOWEVER if your ISR() just has a few ints/chars/uint8/16's defined at the top there about a 90-100% chance that they'll never be allocated in RAM anyway but will be optimised into some of the 32 machine registers. This cannot happen if they are global.
In an ISR, there is always SRAM.
Any registers used are saved on the stack for later restoration.
If locals are not used all at once, the ISR might use fewer registers than suggested by the declarations.
In principle, that could also happen with statics,
but so far as I know, it never does.
Quote:

So I'd suggest you keep anything local to the ISR() that is "small" as stack frame automatics and anything "large" (buffers/arrays) global to guarantee there will be RAM to accommodate them at any time (as there has to be).
Buffers should be global so that other functions can see them.
If there is a buffer other functions do not need to see a,
there is probably a design error.

Making a local global or static will not make a stack crash less likely.
It might even become more likely and harder to diagnose.

Iluvatar is the better part of Valar.

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

Quote:

If there is a buffer other functions do not need to see a,
there is probably a design error.

True enough - my example code above is very contrived to make use of a buffer that is simply local to the ISR() but maybe the scenario I showed might occur somewhere one day?