Near-simultaneous pin change interrupts within a single port

I've rummaged for a concrete answer on this topic without success. Before crawling off to possibly re-invent this experiment I thought I'd float the question here.

Looking at the legacy AVR pin change interrupt mechanism as documented in the associated manuals including the clocked logic pin synchronization snippet, I'm led to believe there is no "interrupt pending" state maintained for an individual port pin. Rather the state is for the entire 8-bit port. If this is indeed the case, simultaneously changing port pins will not result in multiple interrupts but rather one within a given port creating a recognition-disabled window during the period where change interrupts are recognized and dismissed in the hardware implementation. Obvious for truly simultaneously occurring pin change events this isn't an issue as the state change will be coherently visible to the ISR.

However the doc I've seen thus far is rather opaque in this area and the implementation may maintain change interrupt state on a per-pin basis within a given port. The motivation for this question is an application for software recovery of multiple data streams which are asynchronous to each other. Here the data clocks seen by the AVR have undefined phase relationships, each clock edge must be recognized and not be lost due to occurring within a window shared by another pin change interrupt.

Anyone know for certain the behavior in this case or ideally may know where this is clarified in an Atmel app note? Thanks.

That is my interpretation of the pin change interrupt description in the ATmega328p data sheet. The first pin to trigger the interrupt wins, but other pins could change state shortly afterwards, and it would not be possible to determine the order of events.

In this case the order of events isn't required but simply preserving the occurrence of events. I was hopeful the model would be any pin change interrupts which occurred after an initial pin change interrupt was internally recognized simply set an internal flip-flop for recognition after the ISR returns.

A brief look at the newer (post Microchip acquisition) devices appear to have removed this limitation. Eg, ATtiny824:

All PORT pins are asynchronous event system generators. PORT has as many event generators as there are PORT pins in the device.

1 Like

How long takes your ISR to complete? If your events occur with shorter distance then take a faster controller. Otherwise each event causes another interrupt.

Not necessarily! You would need to identify the actual manufacturer date of the silicon chip inside the plastic housing. Might be a batch ID number on the plastic housing. Actually you might need to set up a test setup to identify the old chips from the new.

IMO the description applies to the AVR technology as well, in marketing speech. I still don't understand the TO problem.

I think you are correct about this being a marketing speak thing. They may generate separate events, but will result in a SINGLE interrupt call.

are there separate interrupts for each Port?
do both interrupt pins need to be on the same Port?

The documentation for the pin change interrupt processing contains:

The flag is cleared when the interrupt routine is executed. Alternatively, the flag can be cleared by writing '1' to it.

Such that a flag has scope over all pins in the associated port and the "cleared when the interrupt routine is executed" being rather opaque. That is the auto clear happens irrespective of what the ISR has seen and acted upon. Contrast this with an explicit handshake where ISR sees A has occurred, explicitly dismisses exactly A, then acts upon the event which allows a race with sibling pin B to be either seen by the ISR, dismissed, and acted upon during ISR execution -or- remain pending such that a subsequent ISR will occur. This is the typical model where an ISR acts upon all events visible to it when executing or for those which arrive after the ISR checks pending events simply to cause reenter of the ISR until all events are addressed. But that full port scope flag bit being auto cleared by merely ISR execution (and without explicit ISR communication to silicon) suggests pin change events can be lost. Note one solution is using separate ports for each clock signal, but that doesn't scale well.

It isn't a question of ISR processing speed (which is more than sufficient for the intended application) but rather whether asynchronous pin change events occurring on sibling pins in the same port (and under scope of the single port auto-dismissed interrupt flag) can fail to be recognized.

In your encoder application is a single port pin change interrupt notification being used for one or multiple encoders? For the typical case of a single encoder the interrupts would be mutually synchronous (quadrature).

It appears there is sufficient collective experience cited here to justify an experiment this will meet the immediate need. Appreciate the feedback.

I wasn't suggesting the legacy AVRs had a model change in this respect. Rather later Microchip influenced AVR designs appear to have a redesigned pin change interrupt model (eg: ATtiny824). In addition the level/edge sensitivity, slew rate, etc.. can be selected. Dismissing of a pin change interrupt also appears now to be conventional:

An interrupt request is generated when the corresponding interrupt source is enabled, and the interrupt flag is set.The interrupt request remains active until the interrupt flag is cleared.

What happens, as usual, on entry of the ISR.

The doc implies there is no auto reset of the associated flag, which in the later designs notably is per individual port bit, and must be explicitly cleared. Have a look at section 17.5.10 Interrupt Flags here: ATtiny824. At least that's my reading of the new pin change model which coincidentally appears to correspond to the newer UPDI devices. I haven't brushed with UPDI programmed devices thus far but the pin change model does seem to be more conventional along with the ability for selective trigger mode which can eliminate excessive ISR activity in some use cases.

Let’s breakdown what happens in the ATtiny824 down to gate-level:

If a pin meets the conditions given in the register PORTx.PINnCTRL bit n in register PORTx.INTFLAGS will be set.

Any bit in PORTx.INTFLAGS will raise the interrupt line to the Interrupt Controller.

Any raised interrupt line will cause the Interrupt Controller to signal the CPU.

The CPU will finish the current instruction and signal the Interrupt Controller (the I flag in the Status Register is NOT disabled).

The Interrupt Controller passes the address of the interrupt with the highest priority to the CPU and sets a bit in register CPUINT.STATUS to indicate that an interrupt is ongoing.

At the address that is passed to the CPU is a jump instruction to the interrupt handler.

The interrupt handler will start with saving the necessary working registers (inserted by the compiler).

The first instruction in your program will likely be reading PORTx.INTFLAGS.

If an interrupt occurred at another pin in the same port before reading there’s no way tell the order, and you’ll be free to decide which one to handle first.

When handling one of the interrupts you must reset the corresponding bit in PORTx.INTFLAGS.

If a second interrupt occurs on the same pin before resetting, it will be lost.

The interrupt handler will end with restoring the working registers and reti (inserted by the compiler).

On reti the CPU will resume the program from before the interrupt and signal the Interrupt Controller.

The Interrupt Controller will reset the bit in CPUINT.STATUS and is now free to respond to any interrupts.

The interrupt line from PORTx is still high because of the interrupt of the other pin, so the above will be repeated.

Regrettably as expected given the documented behavior and programming model, there just isn't sufficient HW state information being maintained nor handshake protocol available for an ISR to fully close the race in recognizing multiple mutually asynchronous pin change events occurring on a single port. I don't doubt the empirical data shared here as the window is only that of a few instructions. A limited work-around is again use of separate ports along with the two dedicated external interrupts, for each incoming clock stream, collectively providing a total of 5 race-free inputs for decode of mutually asynchronous streams.

Here is the response from Microchip:

In a scenario where 8 pins of the same port are utilized, if the second pin change is part of
the same group, a timing hazard arises.

Once the Interrupt Service Routine (ISR) clears the Pin Change Interrupt Flag (PCIF), the
second pin change would be recorded. However, if the second pin change occurs before
the first pin change clears the PCIF flag, then the second pin change will not be captured.

I understand that you need different datastreams to monitor, There are 5 specific external
interrupt vector addresses ( including pin change interrupt of each port ) , you can make
use of the 5 different interrupt vector addresses to monitor the input which will be more
reliable.

[EDIT]

I'd asked for a clarification of the "timing hazard" and received this response:

whenever a new pin change interrupt occurs in a sibling pin while the ISR is executing, it will be discarded because the entire port shares the same vector address. As a result, the reliability of the ISR's response becomes uncertain.

Not an concern of sequence but rather the occurrence of pin change events may be lost if using interrupt notification to deduce their occurrence. The window is due to the ISR having no way to atomically detect the state of a port and clear the associated PCIF flag.

The reason for consulting Microchip is even with the documented behavior the race could potentially be closed if the logic implementation internally captured the first application (ISR) port read after the interrupt vector was fetched. The assumption being the cached port value is the information presented to the ISR prior to its dismissal of the port-wide interrupt. And the dismissal was based upon that information. If a concurrent port change event occurs between the cached port read and the ISR dismisses the active interrupt, the implementation would know this concurrent event was not seen by the application and allow the port interrupt to persist even though explicitly cleared by the ISR, but based upon now dated information.

An even simpler implementation could have been a separate I/O register which provides the port data and dismisses all pending port-wide interrupts as a result of the read. This register would only be appropriate for access in an ISR alone but would close the race.

If the phase relationships are mutually asynchronous you would have to repeat that for an undetermined number of times. Or possibly until the ISR detected NO change and takes NO action to dismiss the ISR. However the prevailing mechanism is also documented to be auto-dismissed which preserves the existence of a race.

It is quite mysterious why such an incomplete model was used as this problem has been solved with the simple approach above over a half century ago, where multiple asynchronous interrupt sources are represented and reliably managed by sibling bits in a single register.

Regrettably at this point I don't believe it is possible to effectively convey my concern so I'll leave the discussion here. While empirical observations are indeed valuable guidance they are not necessarily definitive. Given the sketchy documentation along with speculation of how it appears to function, and with the concern based on experience of a model gap, I contacted the manufacturer for a definitive statement whether events could be lost. I thought their response would be of value to share with others here. If you feel an alternative understanding is suitable for your needs, I'll respect your decision.

I do as a rule keep participation in technical discussions off the personal/personality slippery slope. But given the inferred question, I'll entertain it. I've been designing digital systems since the early 70s and take license upon that experience to comment on such matters. Now you know.

The trick is to read the Interrupt Flag Register of that port (PORTx.INTFLAGS) and clear the appropiate interrupt flag as soon as possible.