The fix in EVT_END_SEQ0:
1. ANT_NO_MOD() — silences LF_MOD so the local drive no longer charges the peak detector
2. bsp_delay_ms(2) — 2ms settle, slightly above the ~2ms time constant so the detector drains to reflect only the external field
3. is_lf_field_exists() — now sees the real field state
4. If field gone → nrfx_pwm_stop() → EVT_STOPPED → lf_field_lost() runs correctly
5. If field present → ANT_MOD() restores modulation for the next PWM sequence
Split the single --id argument into --cn (8 ASCII chars) and --raw
(32 hex char T55XX bitstream, directly compatible with PM3 raw output).
Add Python-side PAC bitstream encoder/decoder for raw format support.
Output now shows CN and Raw labels matching PM3's format.
Add NRF_LOG module registration to pac.c for debug logging,
consistent with other protocol implementations.
Reassign PAC command IDs (3014/3015) to avoid collision with ioProx
(3010/3011) after rebase onto upstream/main.
Three fixes that together bring rapid-fire read reliability from ~20%
to 100%:
- Add MIN_SPIKE_CAP floor (8000) to prevent spike_cap from clipping
NRZ high when prescan correctly captures NRZ low. Without this,
spike_cap = raw_min*3 ≈ 2820 collapses the signal range.
- Reorder carrier-before-SAADC in pac_read(): start the 125kHz field
and wait 10ms before enabling ADC sampling, so prescan calibration
sees real NRZ signal levels rather than T55XX power-on-reset noise.
- Add auto-recalibration: if no valid frame is found after 20480
Phase 3 samples (~164ms, ~5 frame periods), reset the decoder to
Phase 1 and re-calibrate from fresh samples. This gives ~3
calibration attempts per 500ms scan window instead of just one.
Tested with Proxmark3 sim (15 consecutive rapid-fire reads, 100%) and
T55XX tag (write-read roundtrip + 15x rapid-fire, 100%).
Add pac_t55xx_writer() for encoding PAC card data into T55XX blocks,
along with the T5577_PAC_CONFIG (NRZ/Direct, RF/32, password-protected,
4 data blocks). Wire DATA_CMD_PAC_WRITE_TO_T55XX (3011) through the
command processor, dispatch table, and Python client.
Replace the 32-sample moving average + hysteresis demodulation with
Proxmark3-inspired per-sample thresholding and dead zone. This
eliminates ~16 samples of group delay per edge, reducing timing
jitter from ~11 samples to ~2-3 samples.
The new approach:
- Prescan: track raw_min, compute spike_cap (unchanged)
- Warmup: track min/max of clipped samples directly (not averaged)
- Detection: per-sample dead zone classification — sample >= high
threshold → 1, sample <= low threshold → 0, between → keep
previous state. Thresholds set at 75% fuzz of signal range.
Removes the avg_buf[32] circular buffer, avg_sum, avg_idx, and
sum-unit threshold/hysteresis state. Struct is 72 bytes smaller.
Widen integer types to prevent overflow UB:
- sample_count: uint16_t -> uint32_t (overflows at 524ms)
- interval, nbits: uint16_t -> uint32_t (matching sample_count width)
Implements NRZ/Direct modulation decoder for PAC/Stanley 125kHz cards
using SAADC ADC sampling with spike-aware threshold calibration.
The LC antenna produces brief high-amplitude transients at NRZ transitions
which are clipped before the moving-average filter to isolate the actual
data levels.