Add LF PAC/Stanley (125kHz) Support#362
Add LF PAC/Stanley (125kHz) Support#362GameTec-live merged 12 commits intoRfidResearchGroup:mainfrom
Conversation
|
You are welcome to add an entry to the CHANGELOG.md as well |
Built artifacts for commit eddbb31FirmwareClient |
|
I just tried this with a PAC/Stanley fob and it worked fine, both reading and emulating. |
|
Great work! Looking forward to this feature making its way into the next release 👍 |
|
I've tried the PR. ChameleonUltra: Seems fine. PM3: I expected to see the string |
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.
Implements NRZ/Direct modulation at RF/32 for PAC/Stanley tag emulation. The modulator encodes 8-byte ASCII card IDs into 128-bit NRZ frames (0xFF sync + 12 UART frames) and generates PWM waveforms using constant output levels (compare=counter_top for HIGH, compare=0 for LOW). Firmware: modulator in pac.c, load/save/factory callbacks in lf_tag_em, tag_emulation registration, SET/GET_EMU_ID commands (5006/5007). CLI: pac_set/get_emu_id methods, 'lf pac econfig' command, hw slot list display for PAC tags.
… integer overflows 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)
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.
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%).
…dle unknown tag types gracefully - Remove lf pac debug command (development-only) - Accept both 16-hex and 8-ASCII card ID formats with 7-bit validation - Add T55xx write command under lf pac write - Handle unknown TagSpecificType values in slot list without crashing - Auto-initialize slot data when setting tag type - Simplify pac_write_to_t55xx by removing unused key parameters
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.
f5264d3 to
f5d721b
Compare
Updated CLI & Rebased onto current mainOld confusing behaviour:
Thanks for catching that @LupusE! The ID string Why did RAW and the ID differ? The raw output of PM3 contains the 8-byte card ID wrapped in a PAC frame:
I agree that the CLI options are confusing and too much magic is happening in the background. I aligned it with what PM3 does and changed it to New CommandsSetting the card number Read this back on pm3 And now with the Reads this back on pm3 Please do give the new build a try, hopefully it works on your CU and PM3 aswell:) |
|
Looks fine for me, tested only virtual on the PM3, no physical access leads to Thanks for the technical explanation! Please don't force-push in future, if not absolutely necessary. We have some changes and it is hard to keep manual track of all little changes for every force-push. |
LupusE
left a comment
There was a problem hiding this comment.
Fine for me, the virtual test against PM3 works!
|
Thank you for reviewing it! I'll avoid force-pushing and rebasing in the future, as tracking changes during merge conflicts was annoying. I'll merge main into my branch in the future instead :) Forgot to update the changelog, will do that and then the PR should be ready |
|
Another CU we had on hand struggled to emulate PAC due to the NRZ encoding (used by PAC/Stanley) and probably different hardware tolerances. @danieltwagner found a fix by using the external oscillator: |
|
Hi @kevihiiin, as I see there are 'conflicts', but I think this only happens because of the CHANGELOG.md ... |
Done:) Thank you for reviewing the PR |
This PR adds (full?) PAC/Stanley reading, emulation and T55XX writing support.
NB: This has only been tested against newer versions with 125kHz fobs. Emulation on the CU does work with our building door reader.
Implementation details
The PAC/Stanley protocl uses ASK and a NRZ type encoding, making the decoding and reading a bit more difficult and error prone. The reader implementation now has a longer calibration phase (allowing for up to 3 attempts within the permitted 500ms timeout).
Verification/Testing
I tested the reader, emlator and tag writer against the Proxmark 3, a physical door access control reader and a T55XX tag.
Reader
The CU reading functionality has been tested against:
Emulating
The CU PAC emulation has been tested against:
lf searchWriting of T55XX tag
Writing of the tag has been tested against:
Test it yourself?
Emulation
I'm new to the nRF ecosystem and RFID. Please give me feedback or comments if you have any!