My MCU died, and rebraining the board seemed like the cleanest way to understand the design. There is almost no useful public documentation on this device, which is a shame, because the engineering is brilliant: a tiny MCU, Schmitt-trigger logic, and a compact LF antenna network all working together with elegant economy. This is a bench repair and reverse-engineering exercise on my own hardware, not a guide for bypassing or testing anyone else’s access-control system.
Note the back traces are overlayed in red. A key one that you can’t see is processor pin 5 goes to the 74HC14 input at U2 pin 3.
Hardware Disassembly
To inspect an HID ProxPass II, a Dremel is required. The sonic‑welded casing must be carefully cut along all four sides, taking care not to slice through the large rectangular loop antenna pressed against the plastic. Once opened, the device reveals a compact piece of 1990s engineering: an active, battery‑powered LF radiator.
The ProxPass II is not a passive HID card with a coin cell. It does not output ordinary Wiegand DATA0/DATA1 pulses. Because the original ProxPass lacked a replaceable battery, the ProxPass II introduced analog tuning modifications to suppress the 125 kHz carrier and concentrate energy into the lower sideband.
This document provides a complete breakdown of the original hardware, the RF protocol, and working C source code to “rebrain” the tag using an ATtiny85.
Hardware Architecture
The logic layer is HID/Wiegand‑like, but the physical layer is an MCU‑generated LF waveform that transmits and receives on the same antenna. The original board architecture is as follows:
Battery Stack → PIC12CE519 (or similar PIC12C5xx) → 4.000 MHz Crystal → 74HC14 Hex Inverter → Q3 Transistor / LC Tank → Loop Antenna
1. The 4.000 MHz Crystal (The Heartbeat)
Timing is critical. The board uses a parallel‑resonant 4.000 MHz ECS crystal with ≈22 pF load capacitors. On this PIC class, the instruction clock is the oscillator frequency divided by four, yielding a 1 MHz instruction clock (1 µs per instruction tick). This makes generating a 125 kHz square‑wave grid mathematically clean: an 8 µs period with 4 µs half‑periods.
2. The 74HC14 Schmitt‑Trigger Hex Inverter (The Glue)
This chip provides Schmitt cleanup, edge sharpening, and electrical isolation between the noisy RF/analog network and the PIC. Its specific hysteresis squares up the slow, noisy field‑detect signals from the antenna, giving the MCU a clean, jitter‑free digital wake interrupt.
3. The Analog Gear Shift (Pin 6 & Transistor Q3)
Pin 6 acts as the master “gear shift”. When the tag wakes, Pin 6 drops LOW exactly 17.6 ms before the RF burst begins. This state change triggers the hex inverter to flood the primary drive capacitor with power, ensuring the active amplifier has enough stored energy regardless of the coin cell’s internal resistance. Simultaneously, it opens Q3 to disconnect an ≈3.8 nF tuning capacitor network from the antenna. This physically detunes the LC tank’s resonant frequency from the 125 kHz listening band up to the 139.6 kHz transmission band. By holding this configuration throughout the burst, Pin 6 turns the antenna into a mechanical bandpass filter perfectly primed to broadcast high‑speed FSK sidebands.
RF Protocol: Phase Slips & FSK
The tag does not phase‑sync to the reader. The reader field simply wakes the device, and the tag asynchronously transmits its own battery‑powered LF FSK burst.
On the MCU’s RF‑drive pin (Pin 5), the waveform is a continuous 125 kHz square wave with deliberate, timed phase slips (missed toggles). Instead of transitioning every 4 µs, the MCU occasionally holds the state for 8 µs.
These slips occur at two specific rhythms:
- ≈32 µs slip intervals (cadence 8) → 15.625 kHz
- ≈40 µs slip intervals (cadence 10) → 12.5 kHz
This generates the standard HID LF FSK sidebands. As confirmed by the FCC filings, the sidebands land precisely at:
| Lower sidebands | Upper sidebands |
|---|---|
| 109.375 kHz | 137.5 kHz |
| 112.5 kHz | 140.625 kHz |
Measured vs. Theoretical Sideband Frequencies
| Lobe avg | Measured (kHz) | Theory (kHz) | Δ (kHz) |
|---|---|---|---|
| 109.69 | 109.375 | 0.315 | |
| 111.605 | 113.52 | 112.5 | 1.02 |
| 137.74 | 137.5 | 0.24 | |
| 139.665 | 141.59 | 140.625 | 0.965 |
These measurements (from the FCC test report) show that the strongest LF components cluster in the upper sideband, with the device formally certified at 139.6 kHz.
Field‑off / Rearm Behavior
The tag is a one‑shot device:
- Enters the reader field → wakes up.
- Delays ≈37 ms.
- Fires one ≈376 ms burst (containing 6–10 repeated frame copies).
- Goes completely silent.
It will not transmit again until it leaves the magnetic field for approximately half a second to reset.
- Minimum reliable ProxMark OFF delay: 519 ms
- Safe value: 520 ms
Pin 4 (wake / enable event) sends a low pulse for at least 1.19 seconds to keep the pin low.
The ATtiny85 Rebrain
If the original MCU dies, an ATtiny85 can be dead bugged onto the board, reusing the original 4 MHz crystal. (Given the standard 8‑pin footprint, other modern MCU equivalents might also drop in as more direct replacements, or someone could rewrite the original PIC assembly using an LLM.) With a 4 MHz crystal, the original MCU’s instruction-cycle cadence lands on an exact divisor of the 125 kHz carrier timing, making bit-level waveform generation unusually clean.
Pin Mapping
| ATtiny85 pin | Function | ProxPass II connection |
|---|---|---|
| Pin 1 (PB5) | VDD (from battery/regulator) | VLOGIC |
| Pin 2 (PB3) | 4.000 MHz crystal (XTAL1) | Y1 pin 2 |
| Pin 3 (PB4) | 4.000 MHz crystal (XTAL2) | Y1 pin 3 |
| Pin 4 (PB2) | Wake / field‑detect input | Pin 7 (original) |
| Pin 5 (PB0) | RF payload bit‑bang output | Pin 5 (original) |
| Pin 6 (PB1) | Analog gate / detune control | Pin 6 (original) |
| Pin 8 (GND) | Ground | GND |
Code
The hardest part was timing: the ATtiny’s “8 MHz internal clock” is not a crystal but an RC oscillator, only roughly ±10% unless calibrated, while the 4.0 MHz ECS quartz crystal is on the order of ±100 ppm, roughly 1,000× tighter, which is why the external crystal finally keeps the 4 µs bitbang grid stable. So we have to put it in 4mhz external crystal mode after we upload the binary. You will have to externally connect the crystal to talk to it again if you change anything.
The code avoids C‑compiler delay loops (which introduce fatal jitter) and instead uses the ATtiny’s internal hardware timer (OCR0A CTC mode) to physically toggle Pin 5. Loops are unrolled, and timer limits are manipulated on the fly to achieve zero‑overhead phase slips that match the original PIC’s timing down to the microsecond.
I used a logic analyzer to pull before/after bits until they exactly matched the original MPU. Here’s the pins on the bitbang side. Again pin 4 is the passive wake pin that charges off the 125khz field. Pin 5 is the bit bang, pin 6 switches to 139.6khz tuning (suppresses lower sideband and carrier). Pin 7 is a “listen” pin, but I didn’t see real use for it other than perhaps verifying it’s 125khz carrier vice anything than pumps pin 4 to wake.
/*
* ATtiny85 4 MHz External Crystal ProxPass Hardware Emulator
*
* ATtiny85 physical pin mapping:
* - Pin 5 = payload waveform output
* - Pin 6 = analog gate / enable / original D6-like control
* - Pin 7 = wake input, active low
* - Pin 2 = crystal
* - Pin 3 = crystal
*
* Behavior:
* - Pin 7 wakes on field-detect low using INT0 low-level wake.
* - Pin 6 performs measured precharge and low-pause timing.
* - Pin 5 emits recovered real 6-frame RF payload cadence.
* - Pin 5 RF payload time: 225.792 ms.
* - Pin 6 goes high 1 us before pin 5 starts transmitting.
* - Pin 6 remains high through the pin 5 payload.
* - After burst, wait POST_BURST_MS, then repeat only if pin 7 is still low.
*
* AFTER YOU UPLOAD to attiny you need to use the 4mhz external clock on proxpass
* avrdude -c usbtiny -p t85 -B 10 -U lfuse:w:0xFD:m <--slow crystal
*
* Then to go back you have to have a 4mhz clock on the burner and do:
* avrdude -c usbtiny -p t85 -B 250 -v
* avrdude -c usbtiny -p t85 -B 250 -U lfuse:w:0xE2:m <-- back to internal 8mhz clock
*/
#define F_CPU 4000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <stdint.h>
/*
* Register-level names:
* - PB0 = physical pin 5
* - PB1 = physical pin 6
* - PB2 = physical pin 7
*/
#define TX_PIN PB0
#define TX_EN_PIN PB1
#define WAKE_PIN PB2
#define FRAMES_PER_BURST 6
#define POST_BURST_MS 1000
/*
* Measured pin 6 sequence:
* - 3.0625 ms after wake before pin 6 precharge high
* - pin 6 high for 48.29475 ms
* - pin 6 low for ~47.54025 ms
* - pin 6 high 1 us before pin 5 payload starts
*/
#define WAKE_TO_PRECHARGE_CYCLES 0UL
#define PRECHARGE_HIGH_CYCLES 193179UL
#define PRE_TX_LOW_PAUSE_CYCLES 145983UL //#deliberately shortened from #define PRE_TX_LOW_PAUSE_CYCLES 190161UL
#define PIN6_LEAD_CYCLES 4UL
#define PIN6_TAIL_CYCLES 0UL
static volatile uint8_t wake_latched = 0;
/*
* Raw 44-bit Frame: 3F8A2B1C701
* Transport hex: 1D5AAA9599599A56A56A5556
*/
static const uint8_t FRAME_CADENCE_PROGMEM[96] PROGMEM = {
8, 8, 8, 10, 10, 10, 8, 10,
8, 10, 8, 10, 10, 8, 10, 8,
10, 8, 10, 8, 10, 8, 10, 8,
10, 8, 8, 10, 8, 10, 8, 10,
10, 8, 8, 10, 10, 8, 8, 10,
8, 10, 8, 10, 10, 8, 8, 10,
10, 8, 10, 8, 8, 10, 8, 10,
8, 10, 10, 8, 10, 8, 10, 8,
8, 10, 8, 10, 8, 10, 10, 8,
10, 8, 10, 8, 8, 10, 8, 10,
8, 10, 8, 10, 8, 10, 8, 10,
8, 10, 8, 10, 8, 10, 10, 8
};
/*
* SRAM cache reduces per-cell overhead during transmit.
* 96 bytes is cheap on ATtiny85 and avoids pgm_read_byte during pin 5 emission.
*/
static uint8_t frame_cadence[96];
ISR(INT0_vect) {
wake_latched = 1;
// Disable wake interrupt immediately so held-low pin 7 does not keep interrupting.
GIMSK &= ~(1 << INT0);
GIFR = (1 << INTF0);
}
static void force_clock_div1(void) {
CLKPR = (1 << CLKPCE);
CLKPR = 0;
}
static void cache_frame_cadence_to_sram(void) {
for (uint8_t i = 0; i < 96; i++) {
frame_cadence[i] = pgm_read_byte(&FRAME_CADENCE_PROGMEM[i]);
}
}
static void timer0_init_hw_ctc(void) {
TCCR0A = (1 << WGM01); // CTC mode, pin 5 disconnected
OCR0A = 15; // 4 us compare interval at 4 MHz
TCCR0B = (1 << CS00); // No prescaler
}
/* ---------- WAKE / SLEEP LOGIC ---------- */
static void wake_disarm(void) {
GIMSK &= ~(1 << INT0);
GIFR = (1 << INTF0);
}
static void wake_arm_low_level_int0(void) {
wake_latched = 0;
// INT0 low-level mode: ISC01 = 0, ISC00 = 0
MCUCR &= ~((1 << ISC01) | (1 << ISC00));
GIFR = (1 << INTF0);
GIMSK |= (1 << INT0);
}
static void wait_for_pin7_high(void) {
while ((PINB & (1 << WAKE_PIN)) == 0) {
_delay_ms(1);
}
}
static void sleep_until_pin7_low(void) {
// Safe muted idle state before sleep.
TCCR0A &= ~(1 << COM0A0);
PORTB |= (1 << TX_PIN); // physical pin 5 idle high
PORTB &= ~(1 << TX_EN_PIN); // physical pin 6 low
wake_disarm();
wake_latched = 0;
// Edge-like behavior: do not arm while pin 7 is already low.
wait_for_pin7_high();
cli();
wake_arm_low_level_int0();
sleep_enable();
if ((PINB & (1 << WAKE_PIN)) == 0) {
wake_latched = 1;
} else {
sei();
sleep_cpu();
}
sleep_disable();
cli();
if ((PINB & (1 << WAKE_PIN)) == 0) {
wake_latched = 1;
}
wake_disarm();
cli();
}
/* ---------- PIN 6 ANALOG GATE SEQUENCE ---------- */
static void pin6_precharge_sequence(void) {
__builtin_avr_delay_cycles(WAKE_TO_PRECHARGE_CYCLES);
PORTB |= (1 << TX_EN_PIN); // physical pin 6 high
__builtin_avr_delay_cycles(PRECHARGE_HIGH_CYCLES);
PORTB &= ~(1 << TX_EN_PIN); // physical pin 6 low
__builtin_avr_delay_cycles(PRE_TX_LOW_PAUSE_CYCLES);
}
static void post_burst_pause(void) {
PORTB &= ~(1 << TX_EN_PIN); // physical pin 6 low
for (uint16_t i = 0; i < POST_BURST_MS; i++) {
_delay_ms(1);
}
}
/* ---------- INTERVAL-CODED REAL PIN 5 TRANSMIT CADENCE ---------- */
static inline __attribute__((always_inline)) void wait_match_set_next(uint8_t next_ocr) {
while ((TIFR & (1 << OCF0A)) == 0);
TIFR = (1 << OCF0A);
OCR0A = next_ocr;
}
static inline __attribute__((always_inline)) void emit_cadence8_repeat(void) {
/*
* cadence 8 repeat physical intervals:
* 4,4,4,4,4,4,8 us
*
* Because OCR0A written after a match affects the NEXT interval,
* the correct command sequence is:
* five 4-us reloads, then request 8 us, then reset to 4 us.
*/
wait_match_set_next(15);
wait_match_set_next(15);
wait_match_set_next(15);
wait_match_set_next(15);
wait_match_set_next(15);
wait_match_set_next(31);
wait_match_set_next(15);
}
static inline __attribute__((always_inline)) void emit_cadence10_repeat(void) {
/*
* cadence 10 repeat physical intervals:
* 4,4,4,4,4,4,4,4,8 us
*
* Seven 4-us reloads, then request 8 us, then reset to 4 us.
*/
wait_match_set_next(15);
wait_match_set_next(15);
wait_match_set_next(15);
wait_match_set_next(15);
wait_match_set_next(15);
wait_match_set_next(15);
wait_match_set_next(15);
wait_match_set_next(31);
wait_match_set_next(15);
}
static inline __attribute__((always_inline)) void emit_cadence8_cell(void) {
for (uint8_t r = 0; r < 12; r++) {
emit_cadence8_repeat();
}
}
static inline __attribute__((always_inline)) void emit_cadence10_cell(void) {
for (uint8_t r = 0; r < 10; r++) {
emit_cadence10_repeat();
}
}
static inline __attribute__((always_inline)) void emit_frame_inline(void) {
uint8_t *p = frame_cadence;
uint8_t i = 96;
do {
uint8_t cadence = *p++;
if (cadence == 8) {
emit_cadence8_cell();
} else {
emit_cadence10_cell();
}
} while (--i);
}
static void emit_6_frames(void) {
emit_frame_inline();
emit_frame_inline();
emit_frame_inline();
emit_frame_inline();
emit_frame_inline();
emit_frame_inline();
}
static void emit_burst(void) {
/*
* Physical pin 6 goes high before physical pin 5 starts.
* Physical pin 5 starts from idle HIGH.
*/
PORTB |= (1 << TX_EN_PIN); // physical pin 6 high
PORTB |= (1 << TX_PIN); // physical pin 5 idle high
__builtin_avr_delay_cycles(PIN6_LEAD_CYCLES);
/*
* Timer0 toggles physical pin 5 on every compare.
* FSK is encoded by changing compare interval:
* OCR0A = 15 -> 4 us
* OCR0A = 31 -> 8 us
*/
TCCR0A = (1 << WGM01);
TCNT0 = 0;
TIFR = (1 << OCF0A);
OCR0A = 15;
TCCR0A = (1 << WGM01) | (1 << COM0A0);
emit_6_frames();
/*
* End of physical pin 5 burst.
* Force physical pin 5 idle HIGH, then drop physical pin 6.
*/
TCCR0A &= ~(1 << COM0A0);
OCR0A = 15;
PORTB |= (1 << TX_PIN); // physical pin 5 idle high
#if PIN6_TAIL_CYCLES > 0
__builtin_avr_delay_cycles(PIN6_TAIL_CYCLES);
#endif
PORTB &= ~(1 << TX_EN_PIN); // physical pin 6 low
}
int main(void) {
cli();
force_clock_div1();
cache_frame_cadence_to_sram();
DDRB |= (1 << TX_PIN) | (1 << TX_EN_PIN);
DDRB &= ~(1 << WAKE_PIN);
PORTB |= (1 << WAKE_PIN); // physical pin 7 pull-up enabled
PORTB |= (1 << TX_PIN); // physical pin 5 idles high
PORTB &= ~(1 << TX_EN_PIN); // physical pin 6 normally low
timer0_init_hw_ctc();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
while (1) {
sleep_until_pin7_low();
/*
* Ignore pin 7 during transmit actions.
* Pin 7 may change because the circuit can no longer hear the carrier.
*/
do {
pin6_precharge_sequence();
emit_burst();
post_burst_pause();
/*
* After the pause, inspect pin 7:
* - If pin 7 is still low, repeat.
* - If pin 7 is high, return to sleep and wait for next field event.
*/
} while ((PINB & (1 << WAKE_PIN)) == 0);
}
}
RESULT
Very hacky rebrain done with very blunt soldering iron and solder. I was able to pick up all feeds except the crystals and pin 5 (bit bang pin) off the P1 EEPROM programmer interface.
Supplemental ProxPass II Connectivity & Functional Summary
Educational / interoperability research only.
P1 Programming Header (ICSP‑like)
| Pin | Net | Connection |
|---|---|---|
| 1 | NC | — |
| 2 | GP1 / ICSPCLK | U3 pin6, U2 pin3 |
| 3 | GP0 / ICSPDAT | U3 pin7, R16 → U2 pin2 |
| 4 | MCLR / VPP / WAKE | U3 pin4, R12 → U2 pin12 |
| 5 | VDD (Vlogic) | U3 pin1, U2 pin14 |
| 6 | GND | U3 pin8, U2 pin7, board ground |
Core ICs
U3 = PIC12CE519 (4 MHz crystal on pins 2‑3)
U2 = 74HC14 (hex Schmitt inverter)
PIC → 74HC14 connections (the logic that matters)
| From U3 | To U2 | Function |
|---|---|---|
| pin6 (GP1) | pin3 (gate2 input) | Primary RF drive control |
| pin5 (GP2) | pin11 (gate5 input) | Soft‑drive / damping control |
| pin7 (GP0) | via R16 from U2 pin2 | Feedback from sense network |
| pin4 (MCLR) | via R12 from U2 pin12 | Wake signal (inverted detector) |
74HC14 outputs
| U2 pin | Drives |
|---|---|
| pin4 (gate2 out) | Q3+Q4 (hard RF switching) |
| pin10 (gate5 out) | R1→antenna clamp node (soft drive) |
| pin2 (gate1 out) | R16→U3 pin7 (sense return) |
| pin12 (gate6 out) | R12→U3 pin4 (wake) |
Unused inputs (pins5,9) grounded.
Wake / Field Detector Path
Antenna right node (E2) → R14 (100k) → diode detector (CR4/CR5/CR1) → common node (CR4 pin3, C11, R15, R13) → R13 (10k) → U2 pin13 (gate6 input) → U2 pin12 (inverted) → R12 (10k) → U3 pin4 / P1_4.
Shunt: R15 (10k) + C11 (~84pF) to ground → low‑pass filter.
When field present, U2 pin13 goes high, pin12 low → PIC sees wake (pin4 low for ≥1.19s).
Rearm requires field‑off ≥520 ms.
Transmit / Antenna Drive
Two parallel paths from U2:
- Hard switch: U2 pin4 → Q3+Q4 → antenna left node (E1) and tuned capacitors.
- Soft drive: U2 pin10 → R1 (330Ω) → C1 (1.456µF) + VR clamp network → antenna right node (E2).
Q4: pin2 ground, pin3 → E1.
Q3: pin2 ground, pin3 → C13/C5/C4/C12 tuning network.
Capacitor clusters (in‑circuit):
- C12+C4 = 3.877 nF (parallel)
- C5+C13 = 3.680 nF (parallel)
C5/C13 other side to ground; C12 other side to E2.
Antenna: rectangle 3.25″×2.3″, 0.008″ wire, 19.2 Ω DC, ~858 µH.
Resonant near 125 kHz receive, detuned to ~139 kHz for transmit upper sideband.
Q1/Q2 Sense / Bias Network
U2 pin1 (gate1 input) connected to Q1 pad3, R6→Q2 pad2.
Q1: pad2=Vlogic, pad1→Q2 pad3.
Q2: pad1→C2 (98nF)+R4 (6.8MΩ), pad2→R6 (470k), pad3→Q1 pad1+R5 (50Ω).
C7 (10.4µF) local decoupling.
U2 pin2 (gate1 output) → R16 (1k) → U3 pin7 (GP0) / P1_3.
This provides a cleaned logic state from the analog bias network.
Key Component Values (Measured in situ)
| Ref | Value | Function |
|---|---|---|
| R1 | 330Ω | Soft‑drive damping |
| R3 | 10k | RF pickup sense |
| R4 | 6.8MΩ | High‑value bias |
| R5 | 50Ω | Local damping |
| R7 | 1MΩ | Top RF region bias |
| R12,13,15 | 10k | Detector conditioning |
| R14 | 100k | Antenna pickup feed |
| R16 | 1k | Feedback from U2 pin2 |
| C1 | 1.456µF | Drive node bypass |
| C2 | 98.4nF | Q2 bias timing |
| C7 | 10.4µF | Local reservoir |
| C11 | 84pF | Detector filter |
| C12+C4 | 3.877nF | Tuning bank |
| C5+C13 | 3.680nF | Tuning bank |
Useful Links
Original Proxpass Repair: HID ProxPass 拆解 & 修理-PIGOO 痞酷網 - Powered by Discuz!
Original PowerProx FCC FCC ID JQ61351
Proxpass 2 FCC: FCC ID JQ61351B


