Reading a pin in an ISR

Hello, I am a complete newbie to the Arduino. This is my first post I have a little programming experience in C++, I use VB at work.

I was thrilled to find this forum...

I would like to know how to check input pins during an interrupt. The quickest way possible.

I have the Arduino Mega and would like to use all 6 interrupts for 6 rotary encoders. I have read and somewhat some of the encoder messages.

I am trying to determine the amount of time an interrupt takes to see if it is feasible to read 6 encoders all possibly running at 16kHz.

Fastest way possible is using "direct port access"

SIGNAL(PCINT0_vect) {
  if (PORTB & 4) {
    upcount++;
  }
}

The ISR entry/exit code produced by the compiler is less than wonderful, however. (saves registers that don't need to be saved, etc.) 96000 interrupts per seconds makes for 166 cycles per interrupt. That's doable, I would think, but not "easy." The above skeleton of an ISR takes about 30 cycles, and you might be able to cut 10 cycles out of entry/exit. What else did you want the arduino to do?

Not to steal your thread, but I'm interested westfw in what methods you could use to cut clock ticks out of the entry/exit? I've got an app running that relies heavily on interrupts and I'm looking for any gains I can make in interrupt efficiency.

-Mitch

Write your own ISR in assembler?

damn.

It does look like AVR-gcc has some features designed to allow more efficient ISRs to be written. (I'm quite pleased to find these, BTW!) The following cuts the original example down quite a bit! (Note that the "meat" of the ISR code is still in C; the only assembler bits are "standard" code for enter an exit. (and that weird bit of global register variable declaration.))

volatile register byte upcount asm("r3");  // dedicated a register to ISR var

ISR(PCINT0_vect, ISR_NAKED) {
  asm( // Standard ISR entry, minus "known zero" setup
  "push r0\n"
    "in r0, 0x3F\n"  // Save status
  "push r0" // Might not be needed
  );

  if (PORTB & 4) {
    upcount++;
  }

  asm( // low-overhead ISR exit...
  "pop r0\n"  // matches "might not be needed"
    "out 0x3f, r0\n"  // restore status
  "pop r0\n"  // restore r0
  "reti\n");

}

(compiles to:

  a6:   0f 92           push    r0
  a8:   0f b6           in      r0, 0x3f        ; 63
  aa:   0f 92           push    r0

  if (PORTB & 4) {
  ac:   2a 99           sbic    0x05, 2 ; 5
    upcount++;
  ae:   33 94           inc     r3
  }

  b0:   0f 90           pop     r0
  b2:   0f be           out     0x3f, r0        ; 63
  b4:   0f 90           pop     r0
  b6:   18 95           reti

This is pretty impressively minimal... Note that the "optional" push/pop of R0 is not necessary in this example!

This is interesting stuff. I'll try it out and post some performance comparisons as soon as I get a chance.

what does this mean? - PORTB & 4 Is it all of PortB or pin 4 of port B...I think I need to read the bit mapping tutorial again.

I think I need to read the bit mapping tutorial again.

Yep!

what does this mean? - PORTB & 4 Is it all of PortB or pin 4 of port B...

"PORTB" is all of port B. "4" is just bit 2 of (whatever.) (because 4 = 2 to the second power.) I probably should have writtenif (PORTB & _BV(2))

I am afraid you are a little over my head. Here is my trial code. I have channel A connected to the interrupt and channel B to pin 22. My servos are 500 CPR and turn a little over 4000 RPM. It seems to work...

My questions are: Can I make this code faster? I think PINA reading all of PORTA...this this correct? How can I read PA0 (pin 22)? Do I need to stop the interrupt to ensure that the EncoderPos var stays correct?

long EncoderPos = 0;
long RPM=0;

 void setup()
 { 
  attachInterrupt(0, ReadEncoder, RISING);
  pinMode (22,INPUT);
  Serial.begin (9600);
 } 

 void loop() 
 {
 Delay(1000);  
 RPM = (EncoderPos/500)*60
 Serial.println(RPM);
 } 

void ReadEncoder()
{
if(PINA)
  {
    EncoderPos++;
  }
  else
  {
    EncoderPos--;
  }
}

Well, it would seem that I know too little to be fooling around with ISR_NAKED. I implemented one of my interrupts using your posted code westfw, and my atmega1280 is halting somewhere early in the program (setup never completes). My interrupt routine is not simple and so I suspect I would need to dig into assembly to figure this out. I'm just not willing to introduce those potential bugs until I can determine I need the efficiency gains (so far my longest interrupt is 200 clock ticks).

But I do thank you for the info.

rlwoodjr:

PINA is 1-byte = 8-bits. Each bit of this byte represents one of the digital inputs on Port A. PA0 is the LSB (least-significant-bit) of the PINA byte. The MSB (most...) is PA7. Here is a binary example. Suppose... PINA == B00001111

This represents PA0..3 are "ON" (digital '1'), and PA4..7 are "OFF" (digital '0'). Knowing this you can use bitmath to isolate the bit you're interested in and determine whether it is ON/OFF. westfw showed you a macro "_BV(n)" which means "Bit-Value@Position_n".

I see your trial code "ReadEncoder()" is comparing all of PINA, which will evaluate as true as long as any one of the bits on PINA is "1". Not what you're after...

Yeah; ISR_NAKED is one of those things where it is nice to know it's there, but is best avoided unless you really really need it!

@rlwood: Your code is probably about as good as you can get while still using the arudino "attachInterrupt" feature. The ISR_NAKED stuff we're talking about would replace attachInterrupt itself.

It's a pretty theoretical discussion. I'm all pleased that it looks like you can probably write as fast an ISR mostly in C as you could in assembler, but that doesn't mean that it's easy, or that it would be "fast enough" for any particular application.

The next step was to add another interrupt to count incoming pulses. It is identical to the first one. Everything works great until the second interrupt pulses over 5 kHz. Then the first encoder starts missing counts and the RPM variable reduces in value.

I eliminated the if(PINA) to the first ISR and the second ISR could pulse to 40 kHz without changing the RPM variable.

So I tried EncoderPos=EncoderPos+PINA. It had the same results...the first encoder starts missing counts and the RPM variable reduces in value.

I will see if I check one pin and see if that gives better results.

I have looked at:http://forums.trossenrobotics.com/tutorials/how-to-diy-128/an-introduction-to-interrupts-3248/

This will take some experimenting for me to understand whats going on...