Source

rfid / avrfid2.c

/** \file
 * AVR RFID card.
 *
 * Based on avrfrid.S by Beth at Scanlime.
 * http://scanlime.org/2008/09/using-an-avr-as-an-rfid-tag/
 *
 * 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 constaints if we tried to make those calls.
 *
 * However, the IJMP only takes 2 clock cycles, so we can build a state
 * machine and use it to make "function calls".  LPM also takes three
 * clocks, so we can't load a full address and jump to it within the
 * timing constraint, but we can split these operations across the
 * ten 5-cycle transitions during sending a baseband 1. 
 *
 * Each of these transitions takes 2 cycles for the XOR and OUT to
 * set the state, which leaves three cycles for our work.
 *
 */
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/sfr_defs.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.
 *
 * These are hard coded in toggle_raw().
 */
volatile register uint8_t r16 __asm__("r16"); 
volatile register uint8_t r17 __asm__("r17"); 

/** r15 tracks which bit are we currently sending.
 *
 * This is hard coded in hid_header().
 */
volatile register uint8_t bit_num __asm__("r15"); 


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


/** Copy a value + offset into z_lo.
 * 1 clock if offset is a constant 0, 2 clocks otherwise.
 */
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));
}


/** Copy a value into z_hi.
 * 1 clock.
 */
static inline void
z_hi(
	uint8_t x
)
{
	__asm__ __volatile__("mov r31, %0" : :  "r"(x));
}


/** Jump to what ever has been stored into Z with z_lo() and z_hi()
 *
 * PC <- Z
 * 2 clocks
 */
static inline void
__attribute__((__noreturn__))
ijmp(void)
{
	__asm__ __volatile__("ijmp");
	while(1); // make gcc happy
}


/**
 * 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
 * to maximize code density.  Doesn't matter for the state machine version,
 * but otherwise the straight-code version would overflow the 8 KB space.
 */
static inline void
__attribute__((__always_inline__))
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");
}


/** Toggle the output pins to change the coil state.
 *
 * The DDRB pins are used to short the coil, which causes
 * an increase in current draw at the reader.
 *
 * 2 clocks.
 */
static void
__attribute__((__always_inline__))
toggle_raw(void)
{
	__asm__ __volatile__(
		"eor r16, r17\n"
		"out %0, r16\n"
		: : "I"(_SFR_IO_ADDR(DDRB))
	);
}


/** Toggle the state of the output pins and delay for some clocks.
 *
 * The toggle_raw() takes 2 clocks, so we delay for the remainder.
 */
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 delays after the last FSK slot will not be
 * done, instead allowing the caller to make use of three 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
}



/** Send a 1 at the baseband layer.
 *
 * This is only used by the header during setup since it must send
 * several 1 bits in a row.  Only the last one computes the next state.
 * There are no delay slots following this function.
 */
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);
				// word indexed, 2 clocks
				__asm__ __volatile__(
					"inc %0\n"
					"inc %0\n"
					: "=r"(bit_num)
				);

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


/** Send the HID header start bits.
 *
 * The HID header is an illegal state in the Manchester encoding
 * used to indicate the start of the packet.
 *
 * The last baseband 1 will load the first state machine function
 * pointer and jump into the statemachine.
 *
 * The last state in the state machine will
 * bring us back here for a continuous loop.
 */
static void
hid_header(void)
{

	baseband_0(1);
	baseband_0(1);
	baseband_0(0);
				// force this operation here
				__asm__ __volatile__ ("eor r15, r15");
				delay(1);

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


/** Output a manchester 0.
 *
 * Output a baseband 0, followed by a baseband 1.
 * During the baseband 1 the Z register will be updated
 * to contain the pointer to the next function in the state machine.
 *
 * After the 1, with one delay slot since ijmp() takes two clocks,
 * we jump to the next state.
 */
static void
_0(void)
{
	baseband_0(1);
	baseband_1_load();
				delay(1);
				ijmp();
}


/** Output a manchester 1.
 *
 * Output a baseband 1, followed by a baseband 0.
 * During the baseband 1 the Z register will be updated
 * to contain the pointer to the next function in the state machine.
 *
 * After the 0, with no delay slots since ijmp() takes two clocks,
 * we jump to the next state.
 */
static void
_1(void)
{
	baseband_1_load();
				delay(3); // 3 delays slots remain
	baseband_0(0);
				ijmp();
}


/** Entry point at 0x0
 *
 * Since we linking with -nostdlib, main needs to be at 0x0.
 * The easiest way to force that with the default linker script
 * is to put it in the .vectors text section.
 */
int
__attribute__((section(".vectors")))
main(void)
{
	r16 = 0;
	r17 = _BV(PINB3) | _BV(PINB4);

	hid_header();

	/* Never returns */
}