rfid / avrfid2.c

/** \file
 * AVR RFID card.
 *
 * Based on avrfrid.S by Beth at Scanlime.
 *
 * Normal C code doesn't really work here since we are limited to
 * very small number of cycles per bit.  The HID Prox cards are
 * FSK modulated with only four or five RF cycles per baseband cycle.
 * Since the AVR RCALL and RET instructions take four clocks each
 * we would miss all of our timing values if we tried to make those calls.
 *
 * Instead a state machine is generated and the IJMP instruction is
 * used.  This only takes two clocks and allows us to avoid RCALL+RET
 * on each bit.
 * In between each FSK toggle we have two, possibly three clocks.
 * Since we can't count on having three, there is always a NOP
 * and then the two user slots.  Since we have up to ten cycles
 * and a guaranteed one/zero or zero/one transition per bit, we
 * can do up to forty instructions during the FSK bits.  We don't
 * need that many.
 *
 * LPM == 3 clocks
 * IJMP == 2 clocks
 */
#include <avr/io.h>
#include <avr/pgmspace.h>

static void _0(void);
static void _1(void);
static void hid_header(void);

#define HID_MFG_CODE        0x01002  // Do not modify
#define HID_SITE_CODE       42
#define HID_UNIQUE_ID       23946     // May be written on the back of the card

static const void * hid_bits[] PROGMEM = {
// HID manufacturer code (20 bits) == 0x01002
_0, _0, _0, _0,
_0, _0, _0, _1, _0, _0, _0, _0,
_0, _0, _0, _0, _0, _0, _1, _0,

// Facility code (8 bits) == 42
_0, _0, _1, _0, _1, _0, _1, _0,

// ID (16 bits) == 23946
_0, _1, _0, _1, _1, _1, _0, _1,
_1, _0, _0, _0, _1, _0, _1, _0,

// Parity
_0,

// And return to the header when we're done
hid_header
};


/* Use r16 and r17 to track the state of the pins */
volatile register uint8_t r16 __asm__("r16"); 
volatile register uint8_t r17 __asm__("r17"); 

/* Which bit are we currently sending? */
volatile register uint8_t bit_num __asm__("r15"); 


/** Load from a flash address pointed to by z_hi:z_lo
 * Z = r31:r30.
 * Duration: 3 clocks.
 */
static inline uint8_t
lpm_z_inc(void)
{
	uint8_t r;
	__asm__ __volatile__(
		"lpm %0, Z+"
		: "=r"(r)
	);
	return r;
}


static inline void
z_lo(
	uint8_t x,
	uint8_t offset
)
{
	__asm__ __volatile__("mov r30, %0" : : "r"(x));

	// Only add the offset if it is not known at compile time
	// or if it is known at compile time and non-zero.
	if (!__builtin_constant_p(offset) || offset != 0)
		__asm__ __volatile__("add r30, %0" : : "r"(offset));
}

static inline void
z_hi(
	uint8_t x
)
{
	__asm__ __volatile__(
		"mov r31, %0" : :  "r"(x)
	);
}


/**
 * Delay a specific number of clock cycles.
 *
 * rjmp is 2 clocks, nop is 1.
 * So do one nop if the delay is an odd value and then rjmp's for n/2.
 */
static inline void
delay(
	const uint8_t n
)
{
	switch (n/2)
	{
	case 8: asm("rjmp .+0");
	case 7: asm("rjmp .+0");
	case 6: asm("rjmp .+0");
	case 5: asm("rjmp .+0");
	case 4: asm("rjmp .+0");
	case 3: asm("rjmp .+0");
	case 2: asm("rjmp .+0");
	case 1: asm("rjmp .+0");
	case 0: break;
	}

	if (n % 2 == 1)
		asm("nop");
}


static void
__attribute__((__always_inline__))
toggle_raw(void)
{
	__asm__ __volatile__(
		"eor r16, r17\n"
		"out 0x17, r16\n"
	);
}

static void
__attribute__((__always_inline__))
toggle(	
	const uint8_t n
)
{
	toggle_raw();

	if (n > 2)
		delay(n-2);
}

#define ZERO_FREQ	4
#define ONE_FREQ	5


/** Send a 0 at the baseband layer.
 *
 * If delay_slot is set, the last FSK slot will not be filled,
 * instead allowing the caller to make use of two extra clock
 * cycles for their own usage.
 */
static void
__attribute__((__always_inline__))
baseband_0(
	uint8_t delay_slot
)
{
	toggle(ZERO_FREQ); // 4
	toggle(ZERO_FREQ); // 8
	toggle(ZERO_FREQ); // 12
	toggle(ZERO_FREQ); // 16
	toggle(ZERO_FREQ); // 20
	toggle(ZERO_FREQ); // 24
	toggle(ZERO_FREQ); // 28
	toggle(ZERO_FREQ); // 32
	toggle(ZERO_FREQ); // 36
	toggle(ZERO_FREQ); // 40
	toggle(ZERO_FREQ); // 44
	toggle(delay_slot ? ZERO_FREQ : 0); // 48
}


static void
__attribute__((__always_inline__))
baseband_1(void)
{
	toggle(ONE_FREQ); //  5
	toggle(ONE_FREQ); // 10
	toggle(ONE_FREQ); // 15
	toggle(ONE_FREQ); // 20
	toggle(ONE_FREQ); // 25
	toggle(ONE_FREQ); // 30
	toggle(ONE_FREQ); // 35
	toggle(ONE_FREQ); // 40
	toggle(ONE_FREQ); // 45
	toggle(ONE_FREQ); // 50
}


/** Send a 1 at the baseband layer.
 *
 * Interleaved with the FSK are the operations to load the next
 * function pointer.  Once the function "returns", the Z register
 * will contain the address of the next function in the state machine.
 */
static void
__attribute__((__always_inline__))
baseband_1_load(void)
{
	toggle(0); //  5
				uint16_t x = (uint16_t) hid_bits;
				uint8_t ptr_lo = x >> 0;
				uint8_t ptr_hi = x >> 8;
				delay(1);

	toggle(0); // 10
				z_lo(ptr_lo, bit_num);
				z_hi(ptr_hi);

	toggle(0); // 15
				uint8_t next_lo = lpm_z_inc();

	toggle(0); // 20
				uint8_t next_hi = lpm_z_inc();

	toggle(0); // 25
				delay(1);
				z_lo(next_lo, 0);
				z_hi(next_hi);

	toggle(0); // 30
				delay(1);
				bit_num += 2; // word indexed, 2 clocks

	toggle(ONE_FREQ); // 35
	toggle(ONE_FREQ); // 40
	toggle(ONE_FREQ); // 45
	toggle(0); // 50
}


/** Send the HID header start bits.
 *
 * The last baseband 1 will load the first bit and off we go.
 */
static void
hid_header(void)
{

	baseband_0(1);
	baseband_0(1);
	baseband_0(0);
				__asm__ __volatile__ ("eor r15, r15");
				delay(1);

	baseband_1();
	baseband_1();
	baseband_1_load();
				delay(1);
				asm("ijmp");
}



#if 0
static void
__attribute__((__noinline__))
//__attribute__((section(".fini8")))
hid_output(void)
{
	header();
	manchester(HID_MFG_CODE, 20);
	manchester(HID_SITE_CODE, 8);
	manchester(HID_UNIQUE_ID, 16);
	manchester(0, 1);
}
#endif

static void
_0(void)
{
	baseband_0(1);
	baseband_1_load();
				delay(1);
				asm("ijmp"); // PC <- Z, 2 clocks
}

static void
_1(void)
{
	baseband_1_load();
				delay(3); // 3 delays slots remain
	baseband_0(0);
				asm("ijmp"); // PC <- Z, 2 clocks
}

int
__attribute__((section(".vectors")))
main(void)
{
	r16 = 0;
	r17 = _BV(PINB3) | _BV(PINB4);

	hid_header();
}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.