@@ -119,11 +119,19 @@ void RfidPn5180_TaskReset(void) {
119119 rfidTaskResetRequested = true ;
120120}
121121
122+ // Number of consecutive failed read attempts before a card is considered removed.
123+ // Counting attempts instead of wall-clock time keeps removal detection fast
124+ // (~200-400 ms at the usual polling rate) while staying immune to single ghost
125+ // misses and to wall-clock gaps caused by task starvation under load.
126+ #define RFID_CARD_REMOVAL_MISS_THRESHOLD 10u
127+
122128void RfidPn5180_Task (void *parameter) {
123129 static PN5180ISO14443 nfc14443 (RFID_CS , RFID_BUSY , RFID_RST );
124130 static PN5180ISO15693 nfc15693 (RFID_CS , RFID_BUSY , RFID_RST );
125131 uint32_t lastTimeDetected14443 = 0 ;
126132 uint32_t lastTimeDetected15693 = 0 ;
133+ uint8_t misses14443 = 0 ;
134+ uint8_t misses15693 = 0 ;
127135 bool cardAppliedCurrentRun = false ;
128136 bool cardAppliedLastRun = false ;
129137 static byte cardId[cardIdSize], lastCardId[cardIdSize];
@@ -160,6 +168,8 @@ void RfidPn5180_Task(void *parameter) {
160168 // Ensure we don't trigger a removal event during reset
161169 lastTimeDetected14443 = millis ();
162170 lastTimeDetected15693 = millis ();
171+ misses14443 = 0 ;
172+ misses15693 = 0 ;
163173 rfidTaskResetRequested = false ;
164174 stateMachine = RFID_PN5180_NFC14443_STATE_RESET ;
165175 }
@@ -196,14 +206,21 @@ void RfidPn5180_Task(void *parameter) {
196206 if (nfc14443.readCardSerial (uid) >= 4 ) {
197207 cardReceived = true ;
198208 stateMachine = RFID_PN5180_NFC14443_STATE_ACTIVE ;
209+ if (misses14443 > 0 ) {
210+ // helps tuning RFID_CARD_REMOVAL_MISS_THRESHOLD: how long do phantom dropouts really last?
211+ Log_Printf (LOGLEVEL_DEBUG , " ISO-14443: read gap of %lu ms bridged after %u missed reads" , millis () - lastTimeDetected14443, misses14443);
212+ misses14443 = 0 ;
213+ }
199214 lastTimeDetected14443 = millis ();
200215 cardAppliedCurrentRun = true ;
201216 } else {
202217 // Reset to dummy-value if no card is there
203218 // Necessary to differentiate between "card is still applied" and "card is re-applied again after removal"
204- // lastTimeDetected14443 is used to prevent "new card detection with old card" with single events where no card was detected
205- if (!lastTimeDetected14443 || (millis () - lastTimeDetected14443 >= 2500 )) {
219+ // A card is only considered removed after several consecutive failed read attempts,
220+ // so single ghost misses don't trigger a removal event.
221+ if (!lastTimeDetected14443 || (++misses14443 >= RFID_CARD_REMOVAL_MISS_THRESHOLD )) {
206222 lastTimeDetected14443 = 0 ;
223+ misses14443 = 0 ;
207224 cardAppliedCurrentRun = false ;
208225 for (uint8_t i = 0 ; i < cardIdSize; i++) {
209226 lastCardId[i] = 0 ;
@@ -236,12 +253,19 @@ void RfidPn5180_Task(void *parameter) {
236253 if (rc == ISO15693_EC_OK ) {
237254 cardReceived = true ;
238255 stateMachine = RFID_PN5180_NFC15693_STATE_ACTIVE ;
256+ if (misses15693 > 0 ) {
257+ // helps tuning RFID_CARD_REMOVAL_MISS_THRESHOLD: how long do phantom dropouts really last?
258+ Log_Printf (LOGLEVEL_DEBUG , " ISO-15693: read gap of %lu ms bridged after %u missed reads" , millis () - lastTimeDetected15693, misses15693);
259+ misses15693 = 0 ;
260+ }
239261 lastTimeDetected15693 = millis ();
240262 cardAppliedCurrentRun = true ;
241263 } else {
242- // lastTimeDetected15693 is used to prevent "new card detection with old card" with single events where no card was detected
243- if (!lastTimeDetected15693 || (millis () - lastTimeDetected15693 >= 2500 )) {
264+ // A card is only considered removed after several consecutive failed read attempts,
265+ // so single ghost misses don't trigger a removal event.
266+ if (!lastTimeDetected15693 || (++misses15693 >= RFID_CARD_REMOVAL_MISS_THRESHOLD )) {
244267 lastTimeDetected15693 = 0 ;
268+ misses15693 = 0 ;
245269 cardAppliedCurrentRun = false ;
246270 for (uint8_t i = 0 ; i < cardIdSize; i++) {
247271 lastCardId[i] = 0 ;
0 commit comments