Trammell Hudson avatar Trammell Hudson committed 80b4ec3 Draft

Lots more comments

Comments (0)

Files changed (1)

  * 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 values if we tried to make those calls.
+ * we would miss all of our timing constaints 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.
+ * 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. 
  *
- * LPM == 3 clocks
- * IJMP == 2 clocks
+ * 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);
 };
 
 
-/* Use r16 and r17 to track the state of the pins */
+/** 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"); 
 
-/* Which bit are we currently sending? */
+/** 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
+/** Load from a flash address pointed to by z_hi:z_lo and increment Z.
+ *
  * Z = r31:r30.
- * Duration: 3 clocks.
+ * 3 clocks.
  */
 static inline uint8_t
 lpm_z_inc(void)
 }
 
 
+/** 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,
 		__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)
-	);
+	__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.
+ *
+ * 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
 )
 }
 
 
+/** 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 0x17, r16\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(	
 
 /** 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
+ * 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
 }
 
 
+
+/** 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(0); // 30
 				delay(1);
-				bit_num += 2; // word indexed, 2 clocks
+				// word indexed, 2 clocks
+				__asm__ __volatile__(
+					"inc %0\n"
+					"inc %0\n"
+					: "=r"(bit_num)
+				);
 
 	toggle(ONE_FREQ); // 35
 	toggle(ONE_FREQ); // 40
 
 /** Send the HID header start bits.
  *
- * The last baseband 1 will load the first bit and off we go.
+ * 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_load();
 				delay(1);
-				asm("ijmp");
+				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
-
+/** 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);
-				asm("ijmp"); // PC <- Z, 2 clocks
+				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);
-				asm("ijmp"); // PC <- Z, 2 clocks
+				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)
 	r17 = _BV(PINB3) | _BV(PINB4);
 
 	hid_header();
+
+	/* Never returns */
 }
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.