millis() ISR

Hello,

I didn't open a new topic for this, just a quick question about millis(), more precisely about the implementation.

Looking at wiring.c

volatile unsigned long timer0_overflow_count = 0;
volatile unsigned long timer0_millis = 0;
static unsigned char timer0_fract = 0;

#if defined(TIM0_OVF_vect)
ISR(TIM0_OVF_vect)
#else
ISR(TIMER0_OVF_vect)
#endif
{
	// copy these to local variables so they can be stored in registers
	// (volatile variables must be read from memory on every access)
	unsigned long m = timer0_millis;
	unsigned char f = timer0_fract;

	m += MILLIS_INC;
	f += FRACT_INC;
	if (f >= FRACT_MAX) {
		f -= FRACT_MAX;
		m += 1;
	}

	timer0_fract = f;
	timer0_millis = m;
	timer0_overflow_count++;
}

unsigned long millis()
{
	unsigned long m;
	uint8_t oldSREG = SREG;

	// disable interrupts while we read timer0_millis or we might get an
	// inconsistent value (e.g. in the middle of a write to timer0_millis)
	cli();
	m = timer0_millis;
	SREG = oldSREG;

	return m;
}

Could someone explain in more detail that why timer0_millis and timer0_fract variables have to be copied into local variables (m and f) inside the TIMER0 ISR?

In tutorials regarding ISR-s, one can read that okay, the variable should be declared as volatile if you'd like to modify it inside an ISR and access it correctly e.g in loop(). If this variable stores a value larger than 8bit (a 2byte int for example) it has to be accessed with interrupts turned off, to avoid data corruption. This two conditions are fulfilled without the local variable copy inside the TIMER0 ISR. What am I missing here?

Thanks in advance.

@amazed

TOPIC SPLIT
PLEASE DO NOT HIJACK / NECRO POST !

Could you take a few moments to Learn How To Use The Forum.

Other general help and troubleshooting advice can be found here.
It will help you get the best out of the forum in the future.

amazed:
Could someone explain in more detail that why timer0_millis and timer0_fract variables have to be copied into local variables (m and f) inside the TIMER0 ISR?

The explanation is in the code - although rather brief.

millis() uses unsigned long variables that consist of 4 bytes. Copying can only happen one byte at time. If you try to copy it while interrupts are active it is possible that one of the bytes might change while the copy is in progress.

...R

It's only to speed up the calculations, because the compiler can emit faster code when it can keep a lot of variables in its internal registers.

@Robin2: Yes, I'm aware of that a variable which larger than 1byte has to be accessed with interrupts turned off (with an 8bit uC ), but this condition is automatically met inside the ISR as far as I know.

My question was rather finding the reason what aarg pointed out.

@aarg: Where is the saving? I mean timer0_millis and timer0_fract globals are copied into registers from the SRAM, if I'm correct. Then after modificiation they are copied back to the SRAM, so SRAM access time is not saved.

I think the main reason of my confusion about this, is the lack of knowledge about the register,SRAM,etc. working mechanism. :slight_smile: Could someone recommend some reading about the topic regarding an 8bit uC for starters?

Thanks in advance.

amazed:
I think the main reason of my confusion about this, is the lack of knowledge about the register,SRAM,etc. working mechanism. :slight_smile: Could someone recommend some reading about the topic regarding an 8bit uC for starters?

Check if the text of this link is helpful to you.

@GolamMostafa: Thanks for the link, I will examine it.

Meanwhile I found this video about SRAM management, it gave me a basic understanding about the topic.

I think I'm getting close to the answer to the question raised in #4 about the calculation time savings. I try to break it down, although I had uncertainty in some lines, so I added question marks also.

First, the method with local variables.

volatile unsigned long timer0_overflow_count = 0;
volatile unsigned long timer0_millis = 0;
static unsigned char timer0_fract = 0;

ISR(TIMER0_OVF_vect)
{
  unsigned long m = timer0_millis;    //4byte SRAM.data access, copy timer0_millis into local variable (first into Stack or directly Registers?)
  unsigned char f = timer0_fract;    //1byte SRAM.data access, copy timer0_fract into local variable (first into Stack or directly Registers?)

  m += MILLIS_INC;                //m = m + MILLIS_INC; --> operation executed in Registers probably
  f += FRACT_INC;                //f = f + FRACT_INC; --> operation executed in Registers probably
  
  if (f >= FRACT_MAX)            //f is checked in Register?
  {        
    f -= FRACT_MAX;                //f = f - FRACT_MAX; --> operation executed in Registers probably
    m += 1;                        //m = m + 1; --> operation executed in Registers probably
  }

  timer0_fract = f;             //1byte SRAM.data access, copy f into volatile gloabal variable (directly from Registers or first into Stack?)
  timer0_millis = m;            //4byte SRAM.data access, copy m into volatile gloabal variable (directly from Registers or first into Stack?)
  timer0_overflow_count++;      //4byte SRAM.data access (or 2x4byte?)
}

This is 14byte SRAM.data access, if m and f local variables only exist in Registers. Is this correct?
If they really only exist in Registers, what should be the case when they get copied to SRAM.Stack?

The same without local copies:

volatile unsigned long timer0_overflow_count = 0;
volatile unsigned long timer0_millis = 0;
static unsigned char timer0_fract = 0;

ISR(TIMER0_OVF_vect)
{
  timer0_millis += MILLIS_INC;                //timer0_millis = timer0_millis + MILLIS_INC; --> 2x4byte SRAM.data access, read and written back after add operation
  timer0_fract += FRACT_INC;                //timer0_fract = timer0_fract + FRACT_INC; --> 2x1byte SRAM.data access, read and written back after add operation
  
  if (timer0_fract >= FRACT_MAX)            //1byte SRAM.data access, it is read again, because it is volatile
  {        
    timer0_fract -= FRACT_MAX;                //timer0_fract = timer0_fract - FRACT_MAX; --> 2x1byte SRAM.data access, read and written back after substract operation
    timer0_millis += 1;                        //timer0_millis = timer0_millis + 1; --> 2x4byte SRAM.data access, read and written back after add operation
  }

  timer0_overflow_count++;      //4byte SRAM.data access (or 2x4byte?)
}

This is 24byte SRAM.data access. Is this correct?

So 10byte SRAM.data access is saved? Which is slower, than access to Registers of course.
I got the sense, that there's more to it..so please, complete/fix/redefine my explanation. :slight_smile:

Thanks in advance.

It's the volatile keyword in their definition that prevents the compiler from caching timer0_millis and timer0_fract in registers. They are global variables so they don't exist on the stack.