Injecting statements before function prologue?

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

I need to disable the pin change interrupt on a tiny84 as soon as the ISR is called:

register uint8_t pcif0 asm("r2");

ISR( PCINT0_vect) {
   PCMSK0 = 0;   // disable further pin change interrupts
   GIFR = pcif0; // clear additional interrupts
   sei();        // enable ints for reset detection
   while ( !(USISR & _BV( USIOIF))) ;  // wait for request byte

   ....
}

int main(int,char**) {
  pcif0 = _BV( PCIF0);
  ....
}

Surprisingly enough, the code above doesn't work (reliably). The reason (AFAIFO) is that it takes a pin change interrupt 4 or 5 clock cycles to propagate through the sync stages. Which means that when I clear the additional interrupts, another pin change interrupt could already be in the pipe (before I cleared the pin change mask) which will trigger the ISR again.

So all I need is a little more time between disabling and clearing the interrupt (say 4 cycles). Unfortunately timing is critical and burning 4 cycles gets me out of the comfort zone.

BUT, if I could inject the disabling of pin change interrupts in front, or into the middle of the function prologue I would have all the time I need. Can that be done, or do I have to descend into the dungeon and write it all in assembler?

Markus

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

What does the .s file for the ISR actually look like? (IOW, how much "stuff" is there in the prologue in fact?)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
.global	__vector_2
	.type	__vector_2, @function
__vector_2:
/* prologue: frame size=0 */
	push __zero_reg__
	push __tmp_reg__
	in __tmp_reg__,__SREG__
	push __tmp_reg__
	clr __zero_reg__
        /* preferred location for the out statement */
	push r18
	push r24
	push r25
	push r30
	push r31
/* prologue end (size=10) */
	out 50-0x20,__zero_reg__

If I could move the out statement to the indicated location I would be a happy man.

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

Well I wonder if there's some way you reduce register usage so there are less push's - maybe by binding vars to specific registers?

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

Once you clear the mask, it does not matter if another event is coming down the pipe... that event will only set the flag, no interrupt will happen because the mask has been cleared. So I would say your reliability issue lies elsewhere.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

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

LOL, that's how I came across the issue to begin with. Notice pcif0, which was the first constant I extracted into a register. Originally the code looked like:

ISR( PCINT0_vect) {
   PCMSK0 = 0;   // disable further pin change interrupts
   GIFR = _BV( PCIF0); // clear additional interrupts 

Which uses an additional

ldi r24, lo(16);

. That one additional clock cycle kept it almost stable (typically for hours).

But my ISR latency was too high, so I started optimizing which soon brought up this issue. It uses the Z register to access an array, r24/25 are working registers and it has to safe a variable (r18). __tmp_reg__ isn't used other than in the prologue and epilog yet it's flushed and restored.

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

glitch wrote:
Once you clear the mask, it does not matter if another event is coming down the pipe... that event will only set the flag, no interrupt will happen because the mask has been cleared. So I would say your reliability issue lies elsewhere.

I think you're right, concerning the interrupt. The problem is that the interrupt flag gets set, and at one point I enable the pin change interrupt again, upon which it would immediately fire. So clearing the flag before I enable the pin change interrupt again should do the trick. Also cuts down another cycle in the latency problem.

Thanks!

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

MegaTorr wrote:
glitch wrote:
Once you clear the mask, it does not matter if another event is coming down the pipe... that event will only set the flag, no interrupt will happen because the mask has been cleared. So I would say your reliability issue lies elsewhere.

I think you're right, concerning the interrupt. The problem is that the interrupt flag gets set, and at one point I enable the pin change interrupt again, upon which it would immediately fire. So clearing the flag before I enable the pin change interrupt again should do the trick. Also cuts down another cycle in the latency problem.

Thanks!

Yes that's right... the flag bits can get set anytime, regardless of the state of the mask bit. So if a flag bit is already set, when you set a mask bit to 1, the interrupt will fire immediately. So you should always clear flag bits just before setting a mask bit, unless you want to process a 'historical' event.

Now you should actually be clearing the bit in GIMSK, not PCMSKn. You may have a propegation delay, and a secondary interrupt, even if a bit is cleared in PCMSKn (looks like 3 cycles from the datasheet schematic and timing diagrams for the PCINT's)

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

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

glitch wrote:
Now you should actually be clearing the bit in GIMSK, not PCMSKn. You may have a propegation delay, and a secondary interrupt, even if a bit is cleared in PCMSKn (looks like 3 cycles from the datasheet schematic and timing diagrams for the PCINT's)
Aha! And that might be what's really biting me. The interrupt does get triggered immediately after I call sei again.

Well, that explains it all. To get out of this I will still have to do both though, disable the pin change interrupt in GIMSK and clear the flag before enabling it again.

Thanks again!
Markus

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

Yes you always need to clear the flag before enabling. And the mask you want to clear/set to control the interrupt is in GIMSK, not PCMSKn. (The PCMSKn bits are only to select which pins can generate the interrupt) Changes to GIMSK are immediate, where as changes to PCMSKn seem to have a few cycle delay.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

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

If something is really *that* time-critical, I'd write it into a separate
assembly file.

Jörg Wunsch

Please don't send me PMs, use email if you want to approach me personally.

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

dl8dtl wrote:
If something is really *that* time-critical, I'd write it into a separate
assembly file.
Which wouldn't have helped me in this case because I would have still run into the same problem.

But in general I agree. If you have to count cycles assembler is the only way to go.