How does a pin interrupt find it's attached ISR?

Status
Not open for further replies.

tcumming

New member
I have four pins set up to trigger interrupts. They are configured using the following attachInterrupts(.. ) calls:

attachInterrupt(digitalPinToInterrupt(FL_MTR_SNSE), pinISRFL, FALLING);
attachInterrupt(digitalPinToInterrupt(FR_MTR_SNSE), pinISRFR, FALLING);
attachInterrupt(digitalPinToInterrupt(RL_MTR_SNSE), pinISRRL, FALLING);
attachInterrupt(digitalPinToInterrupt(RR_MTR_SNSE), pinISRRR, FALLING);

The four interrupt handlers (pinISRFL, .. pinISRRR) get called appropriately. I.e., the code works that's not the problem.

If I look at: https://www.pjrc.com/teensy/interrupts.html#names I see that behind the scenes there's likely only one interrupt vector allocated for pin interrupts. Likely one of the PCINT0_vect or PCINT1_vect. But I'm configuring four interrupt sources to four separate interrupt handlers. This suggests that behind the scenes there's a single pin change ISR that's looking up what pin has changed and matches it to the handler defined by the attachInterrrupt( .. ) call and then calling the correct handler.

I'm asking if my understanding is correct. I don't like code that does magic stuff, and I would prefer to understand how it works. I also have interrupts coming in rather fast. Will this magic code do the right thing if there are multiple pin changes very close together? As an ISR, it's in the fast path. How optimal is this code?

Thx, tom.c
 
there is nothing magic
have a look into pins_teensy.c in cores (I checked with teensy3)
 
Look at the respective data sheet for your device. There is a PinConfigurationRegister (PCRx) for each pin. In that register are bits to set the pin as either Im or Out with additional controls to detect rising, falling and other pin states. There is also an ISR bit in that register that indicates if an interrupt condition occurred. There is also another register that indicates the interrupt status as a 32-bit word with each bit representing one pin in a group of registers.

From my experience when using these lower level registers, they must be reset upon detecting the interrupt and servicing the need. If you wait too long to reset it then you may get strange unexpected behavior.
 
Sort of hard to give a complete answer... Some of it depends on which teensy are you talking about? As both @WNXZ and @grease_lighting mentioned there is no magic, but there is some stuff that happens behind the code... And depending on which teensy you are talking about (I will ignore AVR boards 2.x...).

On some Teensy boards like T3.x there is one ISR per IO port. Extract from attachInterrupt:
Code:
#if defined(KINETISK)
	attachInterruptVector(IRQ_PORTA, port_A_isr);
	attachInterruptVector(IRQ_PORTB, port_B_isr);
	attachInterruptVector(IRQ_PORTC, port_C_isr);
	attachInterruptVector(IRQ_PORTD, port_D_isr);
	attachInterruptVector(IRQ_PORTE, port_E_isr);
	voidFuncPtr* isr_table = getIsrTable(config);


On T4.x by default there is only one interrupt vector for all of the IO pins as we switch the pins to high speed mode:
Code:
	attachInterruptVector(IRQ_GPIO6789, &irq_gpio6789);

When you do the attachInterrupt, the appropriate registers are updated, such that the system will generate the appropriate interrupt. The system sets up it's own ISR handler as you can see in the attachInteruptVector calls above. These functions will be called.
The functions than scan the interrupt status register as well as mask of which ISR's that are supposed to be handled by this code and then it calls the functions you registered for the individual pins.
This method also does the work to make sure that the ISR is not called again for same result (clears the status)...

Is this optimal? depends on your needs. For example on T3.x if you choose your pins carefully such that they are on different IO ports, you can bypass all of this code, by simply calling the appropriate attachInterruptVector code for that port to your own code and process it there. Again you need to clear that state or you end up with an endless calling of your ISR.


On T4.x again by default all of the IO pins funnel to one ISR... Now if you switch the pin from fast speed back to normal speed than the Ports are Ports1-5 instead of 6-9.
For these lower port numbers there are two IRQs per port:
Code:
        IRQ_GPIO1_0_15 =        80,
        IRQ_GPIO1_16_31 =       81,
        IRQ_GPIO2_0_15 =        82,
        IRQ_GPIO2_16_31 =       83,
        IRQ_GPIO3_0_15 =        84,
        IRQ_GPIO3_16_31 =       85,
        IRQ_GPIO4_0_15 =        86,
        IRQ_GPIO4_16_31 =       87,
        IRQ_GPIO5_0_15 =        88,
        IRQ_GPIO5_16_31 =       89,
The controlling of which ports are used is setup in startup.c and controlled by the registers:
Code:
#if defined(__IMXRT1062__)
	// Use fast GPIO6, GPIO7, GPIO8, GPIO9
	IOMUXC_GPR_GPR26 = 0xFFFFFFFF;
	IOMUXC_GPR_GPR27 = 0xFFFFFFFF;
	IOMUXC_GPR_GPR28 = 0xFFFFFFFF;
	IOMUXC_GPR_GPR29 = 0xFFFFFFFF;
#endif
So again one could potentially switch individual pins back to normal speed, plus than setup specific IRQ for that pin using the appropriate ISR for the IO pins Port/pin range...

But again specific to T4.x...
 
I really appreciate a technical answer!

This was a generic question, so I didn't mention a particular cpu. However, I'm using a teensy 3.6 so the answer was definitely apropos.

From your description above, it appears to work exactly the way I imagined. Here's the magic part I was talking about:

The functions than scan the interrupt status register as well as mask of which ISR's that are supposed to be handled by this code and then it calls the functions you registered for the individual pins.
This method also does the work to make sure that the ISR is not called again for same result (clears the status)

I'm considering a use case where I have multiple (4 or more) bits set to interrupt on a falling edge, they are connected to a hall effect sensor on multiple motors that trips twice per revolution. What happens if:

1) An interrupt from one bit comes in.
2) The ISR gets called
3) The code (quoted above) is reading the status register to determine what bit caused the interrupt
4) ... and one of the other bits goes low that should trigger another interrupt.

The above code must be careful handling the interrupt, including clearing the status, etc. so that the second interrupt is not disturbed so that when the ISR returns we'll get the second interrupt. If not, it's a real problem with multiple bits all interrupting frequently as in my use-case.

tom
 
independent interrupts are queued when of same or lower priority and will trigger on the exit of the prior interrupt. The status is monitored/cleared per pin.
 
The above code must be careful handling the interrupt, including clearing the status, etc. so that the second interrupt is not disturbed

The short answer is yes, you can rest assured this scenario is handled properly.

The longer answer involves many pretty deep technical details. But just 1 part I'll mention briefly is the write-1-to-clear approach the hardware uses. The software reads a register that indicates which pins have triggered an interrupt, where a 1 bit means an interrupt has been triggered. The pins which didn't trigger any interrupt have 0 bits.

The interrupt status is reset (so future interrupts can be detected) by writing. But you don't write 0 to set the bit back to 0. Instead, writing 0 has no effect, and writing 1 causes it to reset to zero. The idea is software can just write the bitmask back. The pins which indicated interrupts with 1 bits get set back to zero. But the others aren't affected. If they generated interrupts and became 1 during the time the software was running, they will remain 1s and cause another interrupt and be read next time.

The hardware also has a special feature where it remember is another interrupt has happened on each pin since reading. In that case, writing 1 to set it back to 0 has no effect and it remains 1, so you don't "lose" the new interrupt. The software can just read the status each time the interrupt triggers, use each 1 bit to know that pin generated an interrupt, and write the bitmask back to the hardware. Internally, the hardware is designed so this simple software approach will "just work" because of the 1-to-clear, 0-does-nothing approach and the internal 1 bit queue which remembers if another interrupt happened during that process.

But again, the short version is the hardware and software are designed to work together to automatically handle these situations properly. Normally you never need to worry about how it works internally. But if you are concerned, hopefully this lengthy message helps?
 
It should also be pointed out that there are many ways of doing things like you mentioned.

You are asking about the obvious way of setting up a pin interrupt for each of these pins and either grab time or the like.

Now if you are more interested in timings and the like, then you could potentially use something like the FlexTimer Module and setup to use input capture, which if I remember correctly will grab the system time at the time of the event... So if you can accurate timings with certain caveats like if 16 bit timer you can only go so many units of time before it rolls over... I believe there are some libraries that do this. Example I believe the pulsePosition library (pulsePositionInput) does something like this.
 
Thank you all for your excellent answers.

This is the problem with using high-level API's, they cover up the internal workings making it all magic to the person writing the code. Some of us don't trust magic! Thanks for pulling aside the curtain.

At risk of going off subject; I'm using "elapsedMicros" that introduce even more magic (Grrrr) for timing. I've only started digging into that aspect of the code. As I suspect you've already surmised, I will need to time those interrupts in order to get my motor speed.

Here's the project so far (this actually only a proof-of-concept, the real thing is a bit bigger):

2.jpg
 
Last edited:
It appears that the FlexTimer module can take on input my hall effect sensors, and adjust a pwm without cpu intervention. It looks like the cpu would determine the demand and the FTM would determine the error from demand - hall sensor input and correct the pwm on its own. Using the FTM to generate pulse position to drive simple servo's is almost an insult to its capabilities. However, the pulsePosition code likely will suggest how to configure the FTM module - cool!

I'm still not clear on what the FTM does when a motor stalls or is otherwise not moving so there are no hall input signals. Also, my motors spin both ways. Not sure about that either (yet!).

Thanks for pointing out the FTM and code that uses it. I didn't even know it existed!
 
Status
Not open for further replies.
Back
Top