Skip to content

Instantly share code, notes, and snippets.

@symm
Created November 5, 2016 14:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save symm/65182efcce7c480829e97b898ac3f34d to your computer and use it in GitHub Desktop.
Save symm/65182efcce7c480829e97b898ac3f34d to your computer and use it in GitHub Desktop.
avrfid
/*
* Software-only implementation of a passive low-frequency RFID tag,
* using an AVR microcontroller.
*
* Version 1.1, 2010-06-15
*
* Copyright (c) 2008-2010 Micah Dowty <micah@navi.cx>
* See end of file for license terms. (BSD style)
* Improved HID modulation contributed by Luke Koops <luke.koops@gmail.com>
* HID parity bit support contributed by Cesar Fernandez <cex123@gmail.com>
*
* Supports EM4102-style tags, and the HID 125 kHz prox card protocol.
* The card format and ID number are set below, with #defines.
*
* Basic schematic:
*
* ATtiny85
* +--------------+
* --| RST Vcc |--
* +- L1 ----| B3/CLKI SCK |--
* +---------| B4 MISO |--
* --| GND MOSI |--
* +--------------+
*
* L1 is about 1 mH. It and the AVR are the only components.
* All other pins should be unconnected.
*
* AVR notes:
*
* - Low-voltage parts are better, but I've had success using
* this with the non-extended voltage range parts as well.
*
* - Program the fuses for an external clock with no divider.
* On the ATtiny85, this means setting lfuse to 0xC0.
* Note that after you set this fuse, your programmer will
* need to supply a clock on pin 2 for subsequent programming
* operations.
*
* Optional parts:
*
* - Power decoupling capacitor, between 0.1 and 10uF.
* Bigger is generally better, as it will increase the
* usable range- but if you use this tag with readers that
* have a pulsed carrier wave, bigger caps may take too long
* to charge.
*
* - A load capacitor, in parallel with L1. This will depend
* on your coil. For physically small coils, the internal
* capacitance of the AVR seems to be enough. For larger coils,
* it may be necessary to use a cap here. Experiment to find the
* best value.
*
* - A header, for in-circuit programming. You'll need to expose nearly
* every pin on the chip, since the AVR will also need an external
* clock.
*
* - If you want to make an active (powered) tag, you could hook a 3V
* battery up to the Vcc and GND pins on the AVR. To decrease the power
* usage when idle, you may want to hook a large (a couple megohm)
* pull-down resistor to the clock input, to be sure CLKI doesn't float
* when there is no RF field present.
*
* Theory of operation:
*
* Like all passive RFID tags, this circuit is powered by the 125 kHz
* carrier wave emitted by the RFID reader. In our case, the coil is
* just connected to two AVR I/O pins. We're actually powering the AVR
* through its protective clamping diodes, and the power is retained by
* the AVR die's internal capacitance.
*
* This is a very weak power source, and the AVR typically gets little
* over a volt of Vcc. As a result, most of the AVR's oscillators won't
* start. We can, however, use the carrier wave itself as a clock as well.
* This also makes the software easy, since the instruction clock is also
* the RF clock. We're already clamping the coil voltage into something
* resembles a square wave, so this makes a good external clock source.
*
* To send data back to the reader, passive RFID tags can selectively
* attenuate the reader's carrier wave. Most RFID tags do that with a
* transistor which shorts their coil. We accomplish this by driving the
* coil I/O pins to ground, by toggling the DDRB register. Since the I/O
* drivers on the AVR are weaker than the RF signal, we still get enough
* of a pulse to provide the CLKI input.
*
* And that's about all there is to it. The software is quite simple- we
* are mostly just using assembler macros to convert the desired RFID tag
* code into sequences of subroutine calls which output bits. We can't
* get too fancy with the software, since it's only executing at 125 kHz.
*
*/
/************ Configuration *****************************************/
// Uncomment exactly one format:
#define FORMAT_IS_EM4102
//#define FORMAT_IS_HID
// For the EM4102: An 8-bit manufacturer ID and 32-bit unique ID.
#define EM4102_MFR_ID 0x12
#define EM4102_UNIQUE_ID 0x3456789A
/*
* For the HID card:
* A 20-bit manufacturer code, 8-bit site code, and 16-bit unique ID, 1-bit odd parity.
*
* Manufacturer code is fixed. If modified, HID readers do not recognise the tag.
* (This may also be a kind of fixed header.) Tested on HID readers with 26-bit wiegand output.
*/
#define HID_MFG_CODE 0x01002 // Do not modify
#define HID_SITE_CODE 0x9F
#define HID_UNIQUE_ID 1326 // May be written on the back of the card
/************ Common ************************************************/
#ifndef __ASSEMBLER__
#define __ASSEMBLER__
#endif
#include <avr/io.h>
.global main
#define OUT_PINS _BV(PINB3) | _BV(PINB4)
.macro delay cycles
.if \cycles > 1
rjmp .+0
delay (\cycles - 2)
.elseif \cycles > 0
nop
delay (\cycles - 1)
.endif
.endm
.macro manchester bit, count=1
.if \count
manchester (\bit >> 1), (\count - 1)
.if \bit & 1
baseband_1
baseband_0
.else
baseband_0
baseband_1
.endif
.endif
.endm
.macro stop_bit
baseband_0
baseband_1_last
.endm
/************ EM4102 Implementation *********************************/
/*
* The common EM4102 cards use Manchester encoding, at a fixed rate of
* 64 RF clocks per bit. This means 32 clock cycles per half-bit (baseband
* code). There are a total of 64 manchester-encoded bits per packet. 40
* of these are payload, 9 bits are header (all ones) and one bit is a stop
* bit (zero). All other bits are parity, with one row parity bit every
* 4 bits, and four column parity bits at the end of the packet.
*/
#ifdef FORMAT_IS_EM4102
#define ROW_PARITY(n) ( (((n) & 0xF) << 1) | \
(((n) ^ ((n) >> 1) ^ ((n) >> 2) ^ ((n) >> 3)) & 1) )
#define COLUMN_PARITY ( (EM4102_MFR_ID >> 4) ^ \
(EM4102_MFR_ID) ^ \
(EM4102_UNIQUE_ID >> 28) ^ \
(EM4102_UNIQUE_ID >> 24) ^ \
(EM4102_UNIQUE_ID >> 20) ^ \
(EM4102_UNIQUE_ID >> 16) ^ \
(EM4102_UNIQUE_ID >> 12) ^ \
(EM4102_UNIQUE_ID >> 8) ^ \
(EM4102_UNIQUE_ID >> 4) ^ \
(EM4102_UNIQUE_ID) )
main:
.macro baseband_0
rcall baseband30_0
rjmp .+0
.endm
.macro baseband_1
rcall baseband30_1
rjmp .+0
.endm
.macro baseband_1_last
rcall baseband30_1
rjmp main
.endm
.macro header
manchester 0x1FF, 9
.endm
header
manchester ROW_PARITY(EM4102_MFR_ID >> 4), 5
manchester ROW_PARITY(EM4102_MFR_ID >> 0), 5
manchester ROW_PARITY(EM4102_UNIQUE_ID >> 28), 5
manchester ROW_PARITY(EM4102_UNIQUE_ID >> 24), 5
manchester ROW_PARITY(EM4102_UNIQUE_ID >> 20), 5
manchester ROW_PARITY(EM4102_UNIQUE_ID >> 16), 5
manchester ROW_PARITY(EM4102_UNIQUE_ID >> 12), 5
manchester ROW_PARITY(EM4102_UNIQUE_ID >> 8), 5
manchester ROW_PARITY(EM4102_UNIQUE_ID >> 4), 5
manchester ROW_PARITY(EM4102_UNIQUE_ID >> 0), 5
manchester COLUMN_PARITY, 4
stop_bit
/*
* Emit a 0 at the baseband layer.
* Takes a total of 30 clock cycles, including call overhead.
*/
baseband30_0:
ldi r16, OUT_PINS // 1
rjmp baseband30 // 2
/*
* Emit a 1 at the baseband layer.
* Takes a total of 30 clock cycles, including call overhead.
*/
baseband30_1:
ldi r16, 0 // 1
rjmp baseband30 // 2
/*
* Internal routine for baseband32_0 and _1. Must use
* a total of 24 clock cycles. (32 - 1 ldi - 2 rjmp - 3 rcall)
*/
baseband30:
out _SFR_IO_ADDR(DDRB), r16 // 1
delay 19 // 19
ret // 4
#endif /* FORMAT_IS_EM4102 */
/************ HID Implementation *********************************/
/*
* This works with the HID 125 kHz prox cards I've tested it with,
* but there are undoubtedly other formats used by HID. My cards are
* marked with the model number "HID 0004H".
*
* These cards use both manchester encoding and FSK modulation. The FSK
* modulation represents zeroes and ones using 4 and 5 full RF cycles, respectively.
* An entire baseband bit lasts 50 RF cycles.
*
* Each packet begins with a header consisting of the baseband bit pattern "000111".
* After that, we have 45 manchester-encoded bits before the packet repeats. The
* last bit appears to be a stop bit, always zero. The previous 20 bits encode the
* 6-digit unique ID, which is printed on the back of the card. The other 24 bits
* have an unknown use. They could be a site code or manufacturing code. In the cards
* I've examined, these bits are constant.
*/
#ifdef FORMAT_IS_HID
#define ODD_PARITY(n) ((( ((n) >> 0 ) ^ ((n) >> 1 ) ^ ((n) >> 2 ) ^ ((n) >> 3 ) ^ \
((n) >> 4 ) ^ ((n) >> 5 ) ^ ((n) >> 6 ) ^ ((n) >> 7 ) ^ \
((n) >> 8 ) ^ ((n) >> 9 ) ^ ((n) >> 10) ^ ((n) >> 11) ^ \
((n) >> 12) ^ ((n) >> 13) ^ ((n) >> 14) ^ ((n) >> 15) ^ \
((n) >> 16) ^ ((n) >> 17) ^ ((n) >> 18) ^ ((n) >> 19) ^ \
((n) >> 20) ^ ((n) >> 21) ^ ((n) >> 22) ^ ((n) >> 23) ^ \
((n) >> 24) ^ ((n) >> 25) ^ ((n) >> 26) ^ ((n) >> 27) ^ \
((n) >> 28) ^ ((n) >> 29) ^ ((n) >> 30) ^ ((n) >> 31) ) & 1) ^ 1)
main:
eor r16, r16
ldi r17, OUT_PINS
loop:
/*
* Toggle the output modulation, in the specified number
* of total clock cycles.
*/
.macro toggle clocks
delay (\clocks - 2)
eor r16, r17
out _SFR_IO_ADDR(DDRB), r16
.endm
/*
* Emit a 0 at the baseband layer. (Toggle every 4 cycles, for 50 cycles)
* There was an rjmp that got us to the beginning of the loop, so drop
* 2 cycles from the delay if this is the first bit. That will give the
* appropriate delay before the toggle.
*
* From observing the HID card, each 0 bit is either 48 or 52 cycles.
* The length alternates to keep the average at 50. This keeps the
* waveform smooth, and keeps each bit in its 50 cycle time slot.
*
* We don't have time for a function call, so we just chew
* up lots of flash...
*/
.macro baseband_0
.if startloop
toggle 2 // 4
.equ startloop, 0
.else
toggle 4 // 4
.endif
toggle 4 // 8
toggle 4 // 12
toggle 4 // 16
toggle 4 // 20
toggle 4 // 24
toggle 4 // 28
toggle 4 // 32
toggle 4 // 36
toggle 4 // 40
toggle 4 // 44
toggle 4 // 48
.if evenzero
.equ evenzero, 0
.else
toggle 4 // 52
.equ evenzero, 1
.endif
.endm
/*
* Emit a 1 at the baseband layer. (Toggle every 5 cycles, for 50 cycles)
*/
.macro baseband_1
.if startloop
toggle 3 // 4
.equ startloop, 0
.else
toggle 5 // 4
.endif
toggle 5 // 10
toggle 5 // 15
toggle 5 // 20
toggle 5 // 25
toggle 5 // 30
toggle 5 // 35
toggle 5 // 40
toggle 5 // 45
toggle 5 // 50
.endm
.macro header
.equ evenzero, 0
.equ startloop, 1
baseband_0
baseband_0
baseband_0
baseband_1
baseband_1
baseband_1
.endm
/*
* This should add up to 45 bits.
*
* Some cards may use different 45-bit codes: For example,
* a Wiegand code, or something more site-specific. But the
* cards that I've seen use a 20-bit manufacturer code,
* 8-bit site code, 16-bit unique ID, and a single parity bit.
*
* If your card uses ad ifferent coding scheme, you can add,
* remove, and modify these 'manchester' macros. Just make sure
* the result adds up to the right number of bits.
*/
header
manchester HID_MFG_CODE, 20
manchester HID_SITE_CODE, 8
manchester HID_UNIQUE_ID, 16
manchester ODD_PARITY(HID_MFG_CODE ^ HID_SITE_CODE ^ HID_UNIQUE_ID), 1
rjmp loop
#endif /* FORMAT_IS_HID */
/*****************************************************************/
/*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
# Hey Emacs, this is a -*- makefile -*-
# AVR-GCC Makefile template, derived from the WinAVR template (which
# is public domain), believed to be neutral to any flavor of "make"
# (GNU make, BSD make, SysV make)
MCU = attiny85
FORMAT = ihex
TARGET = avrfid
SRC = avrfid.S
ASRC =
OPT = s
# Name of this Makefile (used for "make depend").
MAKEFILE = Makefile
# Debugging format.
# Native formats for AVR-GCC's -g are stabs [default], or dwarf-2.
# AVR (extended) COFF requires stabs, plus an avr-objcopy run.
DEBUG = stabs
# Compiler flag to set the C Standard level.
# c89 - "ANSI" C
# gnu89 - c89 plus GCC extensions
# c99 - ISO C99 standard (not yet fully implemented)
# gnu99 - c99 plus GCC extensions
CSTANDARD = -std=gnu99
# Place -D or -U options here
CDEFS =
#CDEFS += -DREMOVE_FLASH_BYTE_SUPPORT
#CDEFS += -DREMOVE_EEPROM_BYTE_SUPPORT
#CDEFS += -DREMOVE_FUSE_AND_LOCK_BIT_SUPPORT
#CDEFS += -DREMOVE_AVRPROG_SUPPORT
#CDEFS += -DREMOVE_BLOCK_SUPPORT
# Place -I options here
CINCS =
CDEBUG = -g$(DEBUG)
CWARN = -Wall -Wstrict-prototypes
CTUNING = -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
#CEXTRA = -Wa,-adhlns=$(<:.c=.lst)
CFLAGS = $(CDEBUG) $(CDEFS) $(CINCS) -O$(OPT) $(CWARN) $(CSTANDARD) $(CTUNING) $(CEXTRA)
#ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs
#Additional libraries.
# Minimalistic printf version
PRINTF_LIB_MIN = -Wl,-u,vfprintf -lprintf_min
# Floating point printf version (requires MATH_LIB = -lm below)
PRINTF_LIB_FLOAT = -Wl,-u,vfprintf -lprintf_flt
PRINTF_LIB =
# Minimalistic scanf version
SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min
# Floating point + %[ scanf version (requires MATH_LIB = -lm below)
SCANF_LIB_FLOAT = -Wl,-u,vfscanf -lscanf_flt
SCANF_LIB =
MATH_LIB = -lm
# External memory options
# 64 KB of external RAM, starting after internal RAM (ATmega128!),
# used for variables (.data/.bss) and heap (malloc()).
#EXTMEMOPTS = -Wl,-Tdata=0x801100,--defsym=__heap_end=0x80ffff
# 64 KB of external RAM, starting after internal RAM (ATmega128!),
# only used for heap (malloc()).
#EXTMEMOPTS = -Wl,--defsym=__heap_start=0x801100,--defsym=__heap_end=0x80ffff
EXTMEMOPTS =
#LDMAP = $(LDFLAGS) -Wl,-Map=$(TARGET).map,--cref
LDFLAGS = $(EXTMEMOPTS) $(LDMAP) $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB)
# Programming support using avrdude. Settings and variables.
AVRDUDE_PROGRAMMER = xil
AVRDUDE_PORT = /dev/parport0
AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex
#AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).eep
# Uncomment the following if you want avrdude's erase cycle counter.
# Note that this counter needs to be initialized first using -Yn,
# see avrdude manual.
#AVRDUDE_ERASE_COUNTER = -y
# Uncomment the following if you do /not/ wish a verification to be
# performed after programming the device.
#AVRDUDE_NO_VERIFY = -V
# Increase verbosity level. Please use this when submitting bug
# reports about avrdude. See <http://savannah.nongnu.org/projects/avrdude>
# to submit bug reports.
#AVRDUDE_VERBOSE = -v -v
MY_CLOCK_IS_SLOW = -i 50
AVRDUDE_BASIC = -p $(MCU) -c $(AVRDUDE_PROGRAMMER)
AVRDUDE_FLAGS = $(MY_CLOCK_IS_SLOW) $(AVRDUDE_BASIC) $(AVRDUDE_NO_VERIFY) $(AVRDUDE_VERBOSE) $(AVRDUDE_ERASE_COUNTER) -E noreset
CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
SIZE = avr-size
NM = avr-nm
AVRDUDE = avrdude
REMOVE = rm -f
MV = mv -f
# Define all object files.
OBJ = $(SRC:.c=.o) $(ASRC:.S=.o)
# Define all listing files.
LST = $(ASRC:.S=.lst) $(SRC:.c=.lst)
# Combine all necessary flags and optional flags.
# Add target processor to flags.
ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS)
ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS)
# Default target.
all: build
build: elf hex eep
elf: $(TARGET).elf
hex: $(TARGET).hex
eep: $(TARGET).eep
lss: $(TARGET).lss
sym: $(TARGET).sym
# Program the device.
program: $(TARGET).hex $(TARGET).eep
$(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM)
# Convert ELF to COFF for use in debugging / simulating in AVR Studio or VMLAB.
COFFCONVERT=$(OBJCOPY) --debugging \
--change-section-address .data-0x800000 \
--change-section-address .bss-0x800000 \
--change-section-address .noinit-0x800000 \
--change-section-address .eeprom-0x810000
coff: $(TARGET).elf
$(COFFCONVERT) -O coff-avr $(TARGET).elf $(TARGET).cof
extcoff: $(TARGET).elf
$(COFFCONVERT) -O coff-ext-avr $(TARGET).elf $(TARGET).cof
.SUFFIXES: .elf .hex .eep .lss .sym
.elf.hex:
$(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@
.elf.eep:
-$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
--change-section-lma .eeprom=0 -O $(FORMAT) $< $@
# Create extended listing file from ELF output file.
.elf.lss:
$(OBJDUMP) -h -S $< > $@
# Create a symbol table from ELF output file.
.elf.sym:
$(NM) -n $< > $@
# Link: create ELF output file from object files.
$(TARGET).elf: $(OBJ)
$(CC) $(ALL_CFLAGS) $(OBJ) --output $@ $(LDFLAGS)
# Compile: create object files from C source files.
.c.o:
$(CC) -c $(ALL_CFLAGS) $< -o $@
# Compile: create assembler files from C source files.
.c.s:
$(CC) -S $(ALL_CFLAGS) $< -o $@
# Assemble: create object files from assembler source files.
.S.o:
$(CC) -c $(ALL_ASFLAGS) $< -o $@
# Target: clean project.
clean:
$(REMOVE) $(TARGET).hex $(TARGET).eep $(TARGET).cof $(TARGET).elf \
$(TARGET).map $(TARGET).sym $(TARGET).lss
depend:
if grep '^# DO NOT DELETE' $(MAKEFILE) >/dev/null; \
then \
sed -e '/^# DO NOT DELETE/,$$d' $(MAKEFILE) > \
$(MAKEFILE).$$$$ && \
$(MV) $(MAKEFILE).$$$$ $(MAKEFILE); \
fi
echo '# DO NOT DELETE THIS LINE -- make depend depends on it.' \
>> $(MAKEFILE); \
$(CC) -M -mmcu=$(MCU) $(CDEFS) $(CINCS) $(SRC) $(ASRC) >> $(MAKEFILE)
.PHONY: all build elf hex eep lss sym program coff extcoff clean depend
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment