Skip to content

Shared irq line, guard shared IRQ line against hangs/timeouts - #11

Merged
cparata merged 3 commits into
stm32duino:mainfrom
bartkodden:shared-irq-line
Aug 31, 2026
Merged

Shared irq line, guard shared IRQ line against hangs/timeouts#11
cparata merged 3 commits into
stm32duino:mainfrom
bartkodden:shared-irq-line

Conversation

@bartkodden

Copy link
Copy Markdown
Contributor

Summary

Add an opt-in workaround for configurations where the ST25R3916 IRQ GPIO level cannot be treated as per-device state (for example, two readers sharing a host IRQ input, or additional IRQ-path circuitry).

When ST25R3916_SHARED_IRQ_LINE is enabled, the driver polls this chip’s IRQ status registers in a bounded way so RFAL wait paths do not depend solely on the IRQ pin level.

When the flag is not defined, the upstream code path and behavior are unchanged.


bugs/features fixed

  • Bug 1: Avoid a potential infinite loop in st25r3916CheckForReceivedInterrupts() if the host IRQ line is held HIGH while the selected ST25R3916 has no more IRQ flags to clear (bounded reads under ST25R3916_SHARED_IRQ_LINE).

  • Bug 2: Avoid st25r3916WaitForInterruptsTimed() expiring in poll/shared-IRQ configurations by periodically polling the IRQ status registers to refresh st25r3916interrupt.status.


motivation

In my project I am integrating multiple ST25R3916 devices on a shared SPI bus (separate CS) with an IRQ signal that is effectively shared/unreliable (per-device IRQ state is not guaranteed at the host pin). During bring-up the IRQ path included additional circuitry (level shifting + diode/pull network), which made the GPIO level unsuitable as a strict indicator of the currently selected device’s IRQ state.
My project is built for ESP32 and has an additional layer between my project code and the ST25R3916 repo

On the upstream path, interrupt collection is guarded by:

while (digitalRead(int_pin) == HIGH) { ... }

This assumes the IRQ pin level directly represents the selected ST25R3916. When that assumption does not hold, two problematic cases can occur:

  • If the GPIO reads LOW while the selected ST25R3916 has pending IRQ flags, the loop body never runs and st25r3916interrupt.status may not be updated, so RFAL wait paths can time out.

  • If the GPIO is held HIGH by another device/circuit, the unbounded loop may not terminate because reading this chip’s IRQ registers cannot clear the external assertion.

With ST25R3916_SHARED_IRQ_LINE defined, this PR makes interrupt status collection depend on the selected chip rather than solely on the IRQ pin level:

  • st25r3916CheckForReceivedInterrupts() reads this chip’s IRQ registers at least once per call and bounds repeated reads via ST25R3916_ISR_MAX_REG_READS.

  • st25r3916WaitForInterruptsTimed() periodically calls st25r3916CheckForReceivedInterrupts() (not st25r3916Isr()) so the wait loop can make progress even when no GPIO ISR is updating .status, without dispatching the user callback from the timed wait loop.


Validation

  • Built and smoke-tested on my hardware setup (2× ST25R3916, SPI @ 1 MHz) with ST25R3916_SHARED_IRQ_LINE enabled. This improved stability in my usage (I did not observe hangs during my tests).

  • Built/tested with ST25R3916_SHARED_IRQ_LINE disabled: upstream behavior is preserved (no functional change expected).

Remark
This is my first pr ever, this project has been made possible by ai. I have a working prototype right now. If my solution for this issue is not how it should be handled i would be happy to change my approach.

@fpistm
fpistm requested a review from cparata August 19, 2026 15:13

@cparata cparata left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @bartkodden ,
thanks for your PR. Actually, this library was never tested with 2 instances of the NFC reader device. Unfortunately, I don't have the hardware setup to test this use case on my side. Because the patch is backward compatible, I don't have any particular concern to integrate the PR.
Best Regards,
Carlo

@cparata

cparata commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Hi @bartkodden ,
as you can see, I cannot integrate the PR due to Astyle issue. You must apply the Astyle script to the "st25r3916_interrupt.cpp" file as explained here.
Best Regards,
Carlo

bartkodden and others added 3 commits August 31, 2026 11:08
Upstream gates the IRQ register read on the IRQ pin level, which assumes the
pin belongs to exactly one ST25R3916.  When several ICs share one IRQ input --
or the line is otherwise not trustworthy as per-IC state (wired-AND, diode bus,
a level shifter that holds it) -- the level describes the bus, not the selected
IC, and the guard fails both ways: held de-asserted nothing is ever collected
and every RFAL wait times out; held asserted by a neighbour the while() cannot
terminate, since this IC has nothing left to clear.

Define ST25R3916_SHARED_IRQ_LINE to read this IC's registers unconditionally
once per call and bound the repeat count (ST25R3916_ISR_MAX_REG_READS, default
8).  Undefined, the upstream loop is compiled verbatim -- it stays live code so
it cannot bit-rot, and reverting is undefining one macro.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The wait loop only reads st25r3916interrupt.status and relies on a pin-edge ISR
to fill it in concurrently.  On a shared or untrustworthy IRQ line that edge is
not dependable, so .status stays empty and the loop runs to its timeout on every
transceive.

Define ST25R3916_SHARED_IRQ_LINE to call st25r3916Isr() every
ST25R3916_IRQ_POLL_INTERVAL_US (default 200) while waiting, fetching this IC's
own registers over SPI so progress does not depend on the pin.  Undefined, the
upstream loop is compiled verbatim.

Timing uses micros() from Arduino.h -- already relied on by this file for
digitalRead() -- so the change adds no platform-specific symbol.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…when

polling, since only the status collection is needed and this avoids invoking the
IRQ callback from the timed wait loop.
@fpistm

fpistm commented Aug 31, 2026

Copy link
Copy Markdown
Member

Hi @cparata @bartkodden

I've applied astyle on each commit.
It should be good now.

@cparata
cparata merged commit bdfde71 into stm32duino:main Aug 31, 2026
3 checks passed
@cparata

cparata commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Hi @cparata @bartkodden

I've applied astyle on each commit. It should be good now.

Thanks Frederic!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants