Pinchange and External interrupts together ?

Hi everyone,

I'm using a ProMicro clone of the Leonardo. Today I downloaded EnableInterrupt library 1.0.0.

I got the impression that I would be able to use External and Pinchange interrupts together, but maybe not? or maybe it's a library bug ?

I've got the two types of interrupt working fine separately, but just including the EnableInterrupt library with my external interrupt code gives me compile error

#include <Keyboard.h>
#include <HID.h>
#include <Mouse.h>
#include <EnableInterrupt.h>

#define pinA 3 //Roller A Externasl
#define pinZ 8 //Spinner

long cntX=0; //counter for mouse X-axis
long lastcntX=0;

void setup() {

//pinMode(pinZ, INPUT_PULLUP);
//enableInterrupt(pinZ,doZCount, CHANGE); // PinChange 8us+

pinMode(pinA, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(pinA),doACount,CHANGE); //External 2us+

Mouse.begin();
Keyboard.begin();
}
void loop() {
//send mouse update to usb
if(cntX!=lastcntX ){
Mouse.move((cntX-lastcntX),0);
lastcntX=cntX;
}
}
void doZCount() { // spinner is moving
cntX = cntX +1;
}
void doACount() { // ball is moving
cntX = cntX -1;
}

WInterrupts.c.o (symbol from plugin): In function `attachInterrupt':

(.text+0x0): multiple definition of `__vector_1'

C:\Users\jimbo\AppData\Local\Temp\arduino_build_399664\sketch\sketch_HID_017_TRAK_and_Spin_PCint_v01_bad_02.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here

WInterrupts.c.o (symbol from plugin): In function `attachInterrupt':

(.text+0x0): multiple definition of `__vector_2'

C:\Users\jimbo\AppData\Local\Temp\arduino_build_399664\sketch\sketch_HID_017_TRAK_and_Spin_PCint_v01_bad_02.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here

WInterrupts.c.o (symbol from plugin): In function `attachInterrupt':

(.text+0x0): multiple definition of `__vector_3'

C:\Users\jimbo\AppData\Local\Temp\arduino_build_399664\sketch\sketch_HID_017_TRAK_and_Spin_PCint_v01_bad_02.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here

WInterrupts.c.o (symbol from plugin): In function `attachInterrupt':

(.text+0x0): multiple definition of `__vector_4'

C:\Users\jimbo\AppData\Local\Temp\arduino_build_399664\sketch\sketch_HID_017_TRAK_and_Spin_PCint_v01_bad_02.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here

WInterrupts.c.o (symbol from plugin): In function `attachInterrupt':

(.text+0x0): multiple definition of `__vector_7'

C:\Users\jimbo\AppData\Local\Temp\arduino_build_399664\sketch\sketch_HID_017_TRAK_and_Spin_PCint_v01_bad_02.ino.cpp.o (symbol from plugin):(.text+0x0): first defined here

collect2.exe: error: ld returned 1 exit status

PS. now I've had 5mins to think, I realised and tested that using 6x pinchange interrupts works OK for this project, it doesn't look like I will need the faster external interrupts.

But I'm interested in the answer for the future.

regards
jimmer

You can use both. If memory serves (not always a given), EnableInterrupt will use the pin change interrupt capabilities of the chip EXCEPT if there is an external interrupt attached to the same pin (2 and 3 on the UNO) in which case it will use those instead. This gives the library greater latitude/scope in dealing with interrupts. There is a facility where you can direct the library to NOT use the external interrupts thereby allowing other handlers their use. You need to look at the header file to find out how it's done, but I recall reading it somewhere. EnableInterrupt is quite a useful library and I've found it to be very stable. Good choice if you elect not to handle them yourself.

I've realised that I can test compile for Leonardo, with no board attached, so it's nothing to do with using a clone board. I've logged a bug at the github to see if I can get help there.

I should just leave this be, but the quest for speed is always there! In a way I'm disappointed that the stock routine is fast enough, as I was looking forward to learning how to read and mask the ports.

For info I'm reading a high resolution optical spinner, which gives 600 pulses per rev per channel (2 channels). I can spin it at max 20 rev/s. So 24000 interrupts per second = 40us available to process each. the documentation says about 8us to action the interrupt so plenty of time for the routine and other jobs.

The other 4 channels are much lower pulse rate, and won't be occuring at the same time.

Where did you get your timing information from? Pin change interrupts take a bit more time as you need to mask the bit. If all you're doing is inc/decrementing a counter, and when you're ready, take control of the interrupts, used NAKED ISRs, and you can be in and out in less than 2us.

There is a timing test document inside the enableinterrupt library. It says the old PinchangeInt library gave 13us, new library 8us, NeedForSpeed method 4us and External 2us.

I'm doing quadrature so there is some boolean testing, and also I was using floats. I tested my old ISR code at 25us plus 8us for the call giving 33us which ties up with a physical test I did that failed at around 25 rev/s.

I have now replaced 2 digital reads with reading the port direct, that knocked 8us off.

And I've replaced the floats by ints, which takes the whole routine down to <2us.

So I don't NEED more speed. If I did, rather than speed up the PCints, it must be easier to just get them working with the Extints. Then I can use 2 Extints for the hi-res spinner and 4 slower PCints for the low-res trackball.

I've got PCints working without using the library, so the Extints work as well.

The only problem in my masterplan is that I've realised I can't really put the 4 channels from the trackball onto PCint because there is no minimum gap between the pulse changes from the separate rollers, so I could miss an interrupt in the 8us it takes to get into the ISR and read the Port

Unless: Do I need to read the PORT? The port must have been read to generate the interrupt, so can I just pick up that reading from somewhere?

I'm thinking now, what are bits 7to1 of PCICR ? Is that the port value, or does it tell me which pin caused the interrupt?

Same question for PCIFR, I don't know what that is.

This is what I've got now:

DDRB = 0b01; // pull up
PCICR |= 0b00000001; // set PCIE0
PCMSK0 |= 0b01111110; // pins 6,5,4,3,2,1

ISR( PCINT0_vect ){
PortreadB = PINB;

rollerAB = ( PortreadB & 0b01000100); // pins 6,2
if (rollerAB != oldrollerAB) { doABmove();}
oldrollerAB = rollerAB;

rollerCD = ( PortreadB & 0b00001010); // pins 3,1
if (rollerCD != oldrollerCD) { doCDmove();}
oldrollerCD = rollerCD;
}

What you could maybe look at (I haven't yet timed it) is set up a union/struct,

union
{
    byte port_data;
    struct
    {
        byte pin0:1;
        byte pin1:1;
        byte pin2:1;
        byte pin3:1;
        byte pin4:1;
        byte pin5:1;
        byte pin6:1;
        byte pin7:1;
    };
} port_name;

In this way you read the port into port_name.port_data and the state of each pin is immediately available as port_name.pinX. No masking necessary and then you can test each pin separately.

Thanks. I've added a bit more to my previous message.

I see the problem currently as the 8us gap between the interrupt and me reading the port. If I don't have to read the port (because it's already stored) then I can worry about how fast my <2us ISRs are.

Realistically I think I will put the 4 slow but uncoordinated channels on the ExtInts, and the 2 high res channels on PCint.

I hadn't thought about missing interrupts because I was still in my PCint ISR, that's something to remember if that's what happens. Is it? I'll look into that tomorrow.

Before I got PCint working I was thinking of not using interrupts, just monitoring the port myself. That could work out faster I think eg I could check the port about every 4us , interleaving it with checking for button presses, ... and then speed that process up, :slight_smile: LOL, I could end up as fast as having 6 ExtInts.

Polling is generally faster than using interrupts, especially when you know when to expect the event. External/pin change interrupts are best suited for asynchronous event trapping. You could investigate setting up a timer interrupt to poll the appropriate pins, this is how non-blocking serial comm is implemented.

Another thought to not forget (I mentioned it earlier) is the use of NAKED ISRs. Normally when you enter an ISR you go through an exercise called context switching. All relevant registers are placed on the stack so they can be recovered in order to restore the processor to the same state it was in when the interrupt was fired. IF YOU ARE CERTAIN that your interrupt does not disrupt any registers, your can declare the ISR as NAKED in which case no context switching is perform. This will shave about 1us off the time. If maybe certain but not sure, save the SREG when you enter your ISR and restore it just before you exit, still saves time.

Thanks for that,

When you mentioned __NAKED__ISRs it scared me because from the timing document I read, there are a lot of registers that I'd have to understand whether I need them or not.

I started out with someone else's code, so never spent any time questioning whether interrupts were the best approach. For this job I now don't think they are.

And I don't think I need timers, I will just do a port read on the quadrature pins and process any changes. Then do a port read for my button presses and process those. There is no worry of missing any changes as my loop will be over 10x faster than the fastest pulse rate.

I've learnt quite a lot by not just accepting the first version that seemed to work OK. Its a shame though that right after learning how to do PCints I've decided not to use them.

I still have the hanging question, Do I need to read PortB in my ISR, or can I just grab it's value from somewhere? And what are bits 7to1 of PCICR and PCIFR ?

You can read PORTB anytime you want and from anywhere. Just remember that if an event on PORTB fired an interrupt and you wait too long, the value you read may no longer be valid. Also bear in mind that PORTB is the output register. If you want the pin status of input pins you need to read PINB, the input register.

PCICR is the Pin Change Interrupt Control Register, used to enable/disable PCI on the various ports.

PCIFR is the Pin Change Interrupt Flag Register containing the flags set/cleared when an interrupt occurs/gets serviced. Usually you have nothing to do with this one.

Don't you have a copy of the datasheet?

When you say wait too long to read it, that's exactly what I'm worried about, my PortreadB = PINB; is the first thing in my ISR, but that is still 8us delayed from the interrupt happening. Yeah I could get that down to 2us with a lot more understanding.

I was guessing that complete port state must exist somewhere, but now I realise that's dumb it doesn't make sense for the whole port state to be saved somewhere as the interrupt trigger is just a change on 1 pin.

I do have the datasheet but was struggling to understand it.
My code: PCICR |= 0b00000001; // set PCIE0
I thought I was just setting PCIE0 and didn't care about the other bits.
Did I just get lucky that bits 6 to 1 were already set to 1 (and that's what I need for my 6 PCints) ?

So anyway, what I think now:

PCinterrupts (having only 1 port on ProMicro) is only OK for a single quadrature pair of input. It's OK because I can service the interrupt before another can occur). What about Extints? the trackball won't be used at the same time.

Extints would be OK (if I had 6) for my 3 pairs of quadrature. Because: they won't be missed AND I can process any pin before it can change again AND I can process all pins before any 1 changes again (so no queuing problem).

So this is OK:
4x Extints for the Trackball ( 2 quadrature pairs)
2x PCints for the spinner (1 quadrature pair)

No need to re-write it all using polling.

No. PCICR on the 32U has only bit 0 functional. It enables PCI for the port which is why you need to check the individual bits of the port to see which has caused the interrupt. There's a second register, PCMSK0 that enables/disables PCI on an individual pin, so in your case with PCICR set to 0x01 and PCMSK0 set to 0B01111110 for example, pins [6:1]on the port have interrupts enabled.

Thanks. I actually knew that about the PCMSK0 when I wrote it, but I guess it was tiredness plus wishful thinking hoping I could get more from PCICR that was confusing me :slight_smile:

I just re-jigged my previous post, I now think 4x Extint + 2x PCint is fully resilient against missed pulses. PS. This isn't mission critical software, it's for Missile Command. I'm just getting very into this.