This 125 kHz RFID reader
http://www.serasidis.gr/circuits/RFID_reader/125kHz_RFID_reader.htm
http://www.serasidis.gr/circuits/RFID_reader/images/125kHz_RFID_reader_schem.GIF
I will try to explain with simple words how the RFID works. The ATtiny13 uses the PWM function to produce an 125 kHzsquare wave signal.
This signal comes out from PB0 pin. On the falling edge of the PB0 (Logic '0'), the T1 does not conduct.
So the L1 is energized from R1 (100 ohm) with +5V.
When PB0 pin rises (Logic '1') the T1 conducts and one side of L1 goes to GND.
The L1 goes in parallel with C2 creating an LC oscillator.
These transitions of L1 to logic '1' and logic '0' are made 125000 times in one second (125 kHz).
Data communication between Tag and reader.
How an RFID Tag communicates with the reader?
The idea is simple, but very clever!
When a Tag wants to send a logic '0' to the reader it puts a "load" to its power supply line to request more power from the reader.
That will make a small voltage drop on the RFID reader side.
That voltage level is logic '0' (picture 4).
Simultaneously, as long as the reader transmits the 125 kHz signal it reads the voltage of the transmitted signal trough the filters D1, C3 and R5, C1.
When the Tag drops the voltage as we said before, the reader reads this voltage drop as logic '0'.
When the Tag doesn't require any additional power, it doesn't make a voltage drop.
That is logic '1' (picture 3).
The 'Ones' or 'Zeros' length depends on the serial transmission data rate.
For example, for 125 kHz carrier frequency we don't have 125000 bits per second data transmission!
The data transmission from Tag to the reader varies from 500 bits per second up to 8000 bits per second.
The RFID tag content EM4100
The 125kHz RFID tag transmits 64 bits.
- The first 9 bits are the start communication bits ( always '1' ).
- The next 4 bits are the Low Significant Bits of the customer ID(D00,...,D03).
- The next 1 bit (P0) is the Even parity bit of the previous 4 bits .
- The next 4 bits are the High Significant Bits of the customer ID (D04,...,D07).
- The next 1 bit (P1) is the Even parity bit of the previous 4 bits.
- The next 4 bits are first part of the 32-bit Tag's serial number (D08,...,D11).
- ...
- The PC0 bit is the Even parity bit of bits D00, D04, D08, D12, D16, D20, D24, D28, D32 and D36 (the bits on the same column).
- The PC1, PC2, PC3 bits represent the parity bits of the next 3 columns.
The data verification is been done from ATtiny13 by calculating the Even parity bit of each line and each column with the parity bits that had been received form the RFID Tag transmitted data.
Simple RFID Reader Module Design
http://freshengineer.com/blog/simple-rfid-reader-module-design/
I first started by creating a simple, non-filtered, non-processed reader.
I’ve used a coil of about 1mH for both sides.
Since my chosen frequency was 125 KHz, my capacitor should be 1.62nF according to the following equation; I picked 1n5 standard value.
So this configuration is probably one of the simplest forms of an RFID reader-tag pair:
L1 is driven via a low-impedance 125 KHz oscillator, can be a sine or a square wave
since the LC circuit will filter out the unwanted harmonics that are presented in a square wave.
If the Q of the inductor is high, then a voltage that is greater than the oscillator’s output is going to be present in the “Out”.
I’ve seen 100 Vpp when I fed the LC circuit with 5Vpp!
So, the “Out” waveform at the top of the C1 is a sine wave of a 125 KHz frequency.
Now, the fun thing begins when we put the tag near the reader.
L2, C2 pair picks up the 125KHz waveform via L2. So, if you scope C2, you will see 125 KHz sine wave.
Now, if you scope “Out”, you will see that Vpp at C1 will drop when we close the switch SW1.
That is because we load L1′s magnetic field via L2.
Now, push the button like you are sending a Morse code and watch the “Output” waveform on the scope. Aha, modulation!
Simple! That is how real RFID passive tags work.
However, instead of sending Morse code, they modulate the signal with their specific modulation scheme.
I am going to work with EM4100 protocol since it is widely used.
Okay, let’s bring some real circuitry here.
http://freshengineer.com/Documents/RFID_Reader/KiCad/Outputs/Schematic.pdf
OK, L1 and C6 are our main guys.
They are the components that are mentioned before as “L1″ and “C1″ in Figure 1.
The circuitry on the left side of L1 is used to drive this LC circuit, and right side of C6 is used to read the changes in the signal.
C1 AC couples the clock signal of 125KHz to the circuit.
R1 and R2 biases the transistor Q1.
R4 limits its base current.
Q1 drives the input of push-pull follower formed by Q2 and Q3.
A push-pull follower will drive the signal at low output impedance.
D2 and D3 prevents distortions at the cross-overs from zero level.
Now, our signal at “TP1″ is something like this, with no processing and modulation:
We are going to use an “envelope detector” formed by D4, C8 and R13.
After the recovery, this is how our “modulated” signal looks like:
Of course, these measurements are made with the tag almost touching the reader.
If we move the tag away about 5 cm from the reader, we may not be able to see the signal even with the oscilloscope.
So, we have to filter and amplify this signal and make it ready to be processed by a microcontroller later on.
As you can see above, the signal we are dealing with is an AC signal.
To deal with AC signals with the OP-AMPs, you need either a dual supply which goes to negative (for example -12V, +12V),
or you need a virtual ground.
We are going to assume that half point of our supply voltage is ground.
So, if we are using a 5V single supply, our half point is +2.5V. If +2.5V is ground, then +5V is our new +2.5V and 0V is our new -2.5V.
There you have it, a dual supply.
We need the output impedance of this supply low, so we use an OP-AMP to buffer the +2.5V point
which is high impedance due to R15 and R16, and we get a low impedance output as shown:
OK, now that we have solved that problem, let’s go back to our filter design.
We have a square wave at certain frequency that we want to boost.
While boosting the desired frequency we want to kill the other frequencies.
But we see a bump there; square wave.
A square wave is a signal that includes lots of harmonics (theoretically; infinity) of its actual frequency.
These harmonics are hidden in the rise and fall waves, sharper the rise and fall, more the harmonics count.
So, that means, if you low pass filter a square wave -that is not letting higher frequencies to pass a filter,
you delete those harmonics and remember, those harmonics are in rise and fall times.
Thus, you end up with a sine wave.
We do not want that, that’s why we are going to let these frequencies pass as the way they are, however we are going to boost the original frequency.
To do this, we have a filter design like follows:
“SignalOut” is our input coming from the envelope detector.
C2 and R3 form a high pass filter to AC couple the input, and D1 protects the non-inverting input of the U1:A from over-voltage.
You may say that it is not needed as the capacitor C2 will not allow any DC voltage through, you are correct.
But only in steady state, if the capacitor is discharged, then it will let DC until it is charged.
By the way, think +2.5V point as a “ground” point, since it is a virtual ground.
C5 and R10 AC couples the output from U1:A in case of any DC offset.
Then, this signal is filtered again, resulting in more amplification.
Here is a graph showing the transfer characteristics of these filters:
Here is the waveform at the output node, pin 7 of U1:
Yay! We have a filtered, clean output!
But not so fast, because we need logic output.
This is done easily by a comparator.
Normally, OP-AMP comparators compare the input with a reference voltage, generally half the supply voltage.
However, this may not work well if the rise and fall times of the input waveform is not in symmetry or close enough.
Let’s demonstrate that with a reference voltage of half the supply:
The input signal has a loooong fall time.
It should fall down at 3ms point ideally, since this is a recovered, however badly distorted ~43% duty cycle square wave - well at least let’s assume.
See how the output waveform is a ~56% duty cycle square wave. We do not want that.
What you have to do is simple, compare the input signal with its average.
How do you find a signals average? That is simple too - put it into a low pass filter, and here is the output:
Now let’s look at our case and apply:
Let’s look at R14 and C10, we have selected them so that we have a good averaging (should I say weighted?) level for both 1KHz and 2KHz outputs we will have.
This is the final output, isn’t it great:
Finished PCB:
I am going to cover the digital section, that is the decoding part of this signal, in an another post.
One little hint; it is Manchester coding!
Until then, feel free to comment and share.
DIY FSK RFID Reader
http://playground.arduino.cc/Main/DIYRFIDReader
http://playground.arduino.cc/uploads/Main/FSK-RFID-reader-v2.png
This page describes the construction of an RFID reader using only an Arduino (Nano 3.0 was tested, but others may work),
a hand-wound wire coil, and some assorted low cost common components.
Credits
The hardware and software designs for this project are based in part on the ideas, code and schematics
posted by Micah Dowty here : SIMPLEST RFID READER
and Asher Glick here : AVRFID
Background
RFID readers are devices sold by companies such as Parallax to read RFID tags with embedded identification circuits
(we focus here on passive tags, activated by the reader's transmitted RF energy).
The design presented here shows how to wind a simple wire loop by hand
(or create an equivalent printed circuit spiral version), connect it to an Arduino (or its chip),
add a few low cost common components and create your own RFID reader.
To make it more interesting (i.e. challenging), we will focus on the FSK class of RFID tags,
which are fairly common among the 125kHz devices, but for some reason are not supported by the Parallax kits.
Micah Dowty has shown [ World's simplest RFID reader ] a design for an FSK/ASK RFID reader built around a Parallax Propeller device.
His code, which is in assembly language, implements an ingenious (but complex) algorithm to create a dynamically variable analog bias voltage,
which is used to pull the weak RFID signal into range, so it can be discriminated into binary signals by the Propeller's digital input circuitry.
He also dynamically tweaks the transmit/receive RF frequency to keep the antenna's tank circuit in peak resonance for optimal signal to noise.
All capacitances are in picofarads. C1 and C3 should be 1000 pF, not 1000 nF. Likewise, C2 is 2200 pF.
There are three problems with his approach:
first, the passive detection circuit lacks amplification, which makes it very sensitive to noise and therefore raises reliability issues.
Second, the design is based on the Propeller chip, and if you are a fan of the Arduino and/or associated Atmel AVR chips, it leaves you out.
And third, the dynamic slewing of frequencies and bias voltage is overly complicated, making it hard to debug.
His general concept is attractive, however: use a microcontroller chip and wind your own wire loop to create,
with some simple components and appropriate code, a complete DIY RFID reader.
Asher Glick has presented a solution [ AVRFID ] for reading and decoding FSK RFID tags
using the Arduino/AVR family (which he calls AVRFID),
which is good except it apparently requires obtaining and modifying an existing Parallax RFID reader device
(which natively only supports ASK).
Our goal here is to present a simple solution for reading FSK tags which addresses the above shortcomings:
make it robust and reliable for real-world noise environments, base it on the Arduino,
and build the RFID reader ourselves using a few simple low-cost parts, rather than buying and/or modifying one.
The circuit diagram above was derived from the "World's Simplest RFID Reader" design posted by Micah Dowty.
Based on the Parallax Propeller, Micah's approach was to use passive components only, without amplification,
in order to achieve the ultimate in simplicity.
The lack of amplification, however, results in a weak signal, potentially less than 2V PTP.
This signal is then biased by an analog level produced by the Propeller,
to try to maintain the signal's DC level near the discrimination point of the Propeller's binary-digital input circuitry.
His code attempts to dynamically calculate that optimal midpoint level, and feed it into the circuit using a filtered PWM DAC output.
Since the signal is weak, it can be distorted by interference and noise, which results in reduced reliability.
The circuit presented here includes (as Micah suggests in his documentation) one active component:
a common low-cost LM234quad-opamp IC (or equivalent).
This addition provides several significant advantages, at a negligible cost.
First, the signal is amplified (using one of the four opamps on the IC package) to a more noise-immune level (of 2-3 volts PTP).
Second, the DC level of the signal is maintained at exactly Vcc/2 using another opamp on the IC,
which eliminates the need for the DC propping code in the Arduino.
Third, having the signal amplifier in place allows another low-pass RC filter stage (another capacitor and resistor),
which makes the final discriminated digital signal cleaner and more reliable.
The end result is a more robust detected signal with improved noise immunity.
As a quick review of the circuit, the loop is made of a toroidally-wound #22-30 magnet wire
(we used an empty roll of Scotch 3.25" I.D. packing tape as former),
and can be remoted from the circuit if needed, via coaxial cable.
The inductor L1 and capacitance C1 should be matched to resonate at around 125 kHz.
When driven at its resonant frequency by the Arduino's 0-5 volt square wave signal,
the center point of the resonator (which connects to D1's cathode) will have a fairly pure voltage sine wave, of about 30V PTP.
When coupled to an RFID tag, the pure sine wave RF will fluctuate visibly as the tag opens and closes its own loop antenna to repeatedly transmit its code.
This modulation is then detected from the RF envelope by D1, C2 and R1,
which produce a negative bias voltage with the small detected coded signal, e.g. about 11 RF cycles per coded cycle.
The coded cycles are of two different wave lengths (or frequencies), which represent streams of logic ones and zeros,
and they need to arrive at the Arduino chip as binary levels which can be timed reasonably accurately
so as to reliably tell the difference between the two distinct frequencies.
The relatively large capacitor C3 decouples the negative bias voltage from the signal,
and is followed by a low-pass RC filter stage (R2 and C4) which attenuates some of the residual RF spikes
from the lower frequency coded RFID signal.
Capacitor C5 decouples the resulting signal and presents it to the amplification stage, implemented by the LM324opamp, IC1.
The latter amplifies the weak signal from about .15V to about 3V PTP (depending of the ratio of R4 to R3),
and places it on top of a Vcc/2 bias voltage, about 2.5V in the arduino's case.
This signal is then fed into one of the digital input ports on the Arduino (which also includes some helpful hysteresis),
and is discriminated by the internal comparator into a square wave of ones and zeroes.
Software
The Arduino sketch, derived from the code posted by Asher Glick, [ https://github.com/AsherGlick/AVRFID ]
uses a single timer channel in the Arduino (using the Timer1 library) for both RF signal generation
as well as timing clock to count the width of each input signal wave.
1 /***************************************************************************** 2 | This program was written by Asher Glick aglick@tetrakai.com | 3 | This program is currently under the GNU GPL licence | 4 *****************************************************************************/ 5 6 /****************** CHIP SETTINGS ****************** 7 | This program was designed to run on an ATMEGA328 | 8 | chip running with an external clock at 8MHz | 9 ***************************************************/ 10 11 /********** FUSE SETTINGS ********** 12 | Low Fuse 0xE2 | 13 | High Fuse 0xD9 | +- AVRDUDE COMMANDS -+ 14 | Extra Fuse 0x07 | | -U lfuse:w:0xe0:m | 15 | | | -U hfuse:w:0xd9:m | 16 | These fuse calculations are | | -U efuse:w:0xff:m | 17 | based off of the usbtiny AVR | +--------------------+ 18 | programmer. Other programmers | 19 | may have a different fuse number | 20 ***********************************/ 21 22 /************************** AVRDUDE command for 8MHz ************************** 23 | sudo avrdude -p m328p -c usbtiny -U flash:w:myproject.hex | 24 | -U lfuse:w:0xE2:m -U hfuse:w:0xD9:m -U efuse:w:0x07:m | 25 | | 26 | NOTE: when messing with fuses, do this at your own risk. These fuses for the | 27 | ATMEGA328P (ATMEGA328) worked for me, however if they do not work for | 28 | you, it is not my fault | 29 | NOTE: '-c usbtiny' is incorrect if you are using a different programmer | 30 ******************************************************************************/ 31 32 /******************************* CUSTOM SETTINGS ****************************** 33 | Settings that can be changed, comment or uncomment these #define settings to | 34 | make the AVRFID code do different things 35 ******************************************************************************/ 36 37 //#define Binary_Tag_Output // Outputs the Read tag in binary over serial 38 #define Hexadecimal_Tag_Output // Outputs the read tag in Hexadecimal over serial 39 //#define Decimal_Tag_Output // Outputs the read tag in decimal 40 41 #define Manufacturer_ID_Output // The output will contain the Manufacturer ID (NOT IMPLEMENTED) 42 #define Site_Code_Output // The output will contain the Site Code (NOT IMPLEMENTED) 43 #define Unique_Id_Output // The output will contain the Unique ID 44 45 //#define Split_Tags_With '-' // The character to split tags pieces with 46 47 //#define Whitelist_Enabled // When a tag is read it will be compaired 48 // against a whitelist and one of two functions 49 // will be run depending on if the id matches 50 51 52 // some conststents 53 54 // These values may need to be changed depending on the servo that you are using 55 #define SERVO_OPEN 575 // open signal value for the servo 56 #define SERVO_CLOSE 1000 // close signal value for the servo 57 58 59 //20-bit manufacturer code, 60 //8-bit site code 61 //16-bit unique id 62 63 #define MANUFACTURER_ID_OFFSET 0 64 #define MANUFACTURER_ID_LENGTH 20 65 66 #define SITE_CODE_OFFSET 20 67 #define SITE_CODE_LENGTH 8 68 69 #define UNIQUE_ID_OFFSET 28 70 #define UNIQUE_ID_LENGTH 16 71 72 73 74 // these settings are used internally by the program to optimize the settings above 75 #ifndef serialOut 76 #define serialOut 77 #endif 78 79 80 /// Begin the includes 81 82 #include <avr/io.h> 83 #include <avr/interrupt.h> 84 #include <stdlib.h> 85 86 #define ARRAYSIZE 900 // Number of RF points to collect each time 87 88 char * begin; // points to the bigining of the array 89 int * names; // array of valid ID numbers 90 int namesize; // size of array of valid ID numbers 91 volatile int iter; // the iterator for the placement of count in the array 92 volatile int count; // counts 125kHz pulses 93 volatile int lastpulse; // last value of DEMOD_OUT 94 volatile int on; // stores the value of DEMOD_OUT in the interrupt 95 96 /********************************* ADD NAMES ********************************* 97 | This function add allocates the ammount of memory that will be needed to | 98 | store the list of names, and adds all the saved names to the allocated | 99 | memory for use later in the program | 100 *****************************************************************************/ 101 void addNames(void) { 102 namesize = 2; // number of IDs in the access list 103 names = malloc (sizeof(int) * namesize); 104 // change or add more IDs after this point 105 names [0] = 12345; 106 names [1] = 56101; 107 } 108 109 /******************************* INT0 INTERRUPT ******************************* 110 | This ISR(INT0_vect) is the interrupt function for INT0. This function is the | 111 | function that is run each time the 125kHz pulse goes HIGH. | 112 | 1) If this pulse is in a new wave then put the count of the last wave into | 113 | the array | 114 | 2) Add one to the count (count stores the number of 125kHz pulses in each | 115 | wave | 116 ******************************************************************************/ 117 ISR(INT0_vect) { 118 //Save the value of DEMOD_OUT to prevent re-reading on the same group 119 on =(PINB & 0x01); 120 // if wave is rising (end of the last wave) 121 if (on == 1 && lastpulse == 0 ) { 122 // write the data to the array and reset the cound 123 begin[iter] = count; 124 count = 0; 125 iter = iter + 1; 126 } 127 count = count + 1; 128 lastpulse = on; 129 } 130 131 /************************************ WAIT ************************************ 132 | A generic wait function | 133 ******************************************************************************/ 134 void wait (unsigned long time) { 135 long i; 136 for (i = 0; i < time; i++) { 137 asm volatile ("nop"); 138 } 139 } 140 141 ////////////////////////////////////////////////////////////////////////////// 142 //////////////////////////// SERIAL COMMUNICATION //////////////////////////// 143 ////////////////////////////////////////////////////////////////////////////// 144 145 /******************************** USART CONFIG ******************************** 146 | USART_Init(void) initilizes the USART feature, this function needs to be run | 147 | before any USART functions are used, this function configures the BAUD rate | 148 | for the USART and enables the format for transmission | 149 ******************************************************************************/ 150 #define FOSC 8000000 // Clock Speed of the procesor 151 #define BAUD 19200 // Baud rate (to change the BAUD rate change this variable 152 #define MYUBRR FOSC/16/BAUD-1 // calculate the number the processor needs 153 void USART_Init(void) { 154 unsigned int ubrr = MYUBRR; 155 /*Set baud rate */ 156 UBRR0H = (unsigned char)(ubrr>>8); 157 UBRR0L = (unsigned char)ubrr; 158 /*Enable receiver and transmitter */ 159 UCSR0B = (1<<RXEN0)|(1<<TXEN0); 160 /* Set frame format: 8data, 2stop bit */ 161 UCSR0C = (1<<USBS0)|(3<<UCSZ00); 162 } 163 164 /******************************* USART_Transmit ******************************* 165 | The USART_Transmit(int) function allows you to send numbers to USART serial | 166 | This function only handles numbers up to two digits. If there is one digit | 167 | the message contains a space, then the digit converted to ascii. If there | 168 | are two digits then the message is the first digit followed by the seccond | 169 | If the input is negative then the function will output a newline character | 170 ******************************************************************************/ 171 void USART_Transmit(char input ) 172 { 173 while ( !( UCSR0A & (1<<UDRE0)) ); 174 // Put the value into the regester to send 175 UDR0 = input; 176 } 177 ////////////////////////////////////////////////////////////////////////////// 178 ////////////////////////// BASE CONVERSION FUNCTIONS ///////////////////////// 179 ////////////////////////////////////////////////////////////////////////////// 180 char binaryTohex (int four, int three, int two, int one) { 181 int value = (one << 0) + (two << 1) + (three << 2) + (four << 3); 182 if (value > 9) return 'A' + value - 10; 183 return '0' + value; 184 } 185 186 /*********************** GET HEX ARRAY FROM BINARY ARRAY ********************** 187 | 188 ******************************************************************************/ 189 int * getHexFromBinary (int * array, int length, int * result) { 190 int i; 191 int resultLength = (length+3)/4; // +3 so that the resulting number gets rounded up 192 // 4 / 4 = 1 [correct] 193 // 7 / 4 = 1 (4+3) [still correct] 194 // 5 / 4 = 1 [not rounded up] 195 // 8 / 4 = 2 (5+3) [correct] 196 197 for (i = 0; i < resultLength; i++) { 198 result[i*4] = (array[i+0] << 0) 199 + (array[i+1] << 1) 200 + (array[i+2] << 2) 201 + (array[i+3] << 3); 202 } 203 return result; 204 } 205 /*************************** GET DECIMAL FROM BINARY ************************** 206 | This function will take in a binary input and return an intiger with the | 207 | corrisponding value, assumed as decimal | 208 ******************************************************************************/ 209 int getDecimalFromBinary (int * array, int length) { 210 int result = 0; 211 int i; 212 for (i = 0; i < length; i++) { 213 result = result<<1; 214 result += array[i]&0x01; 215 } 216 return result; 217 } 218 219 220 221 void recurseDecimal (unsigned int val) { 222 if (val > 0 ) { 223 recurseDecimal(val/10); 224 USART_Transmit('0'+val%10); 225 } 226 return; 227 } 228 229 void printDecimal (int array[45]) { 230 #ifdef Manufacturer_ID_Output 231 int manufacturerId = getDecimalFromBinary( array + MANUFACTURER_ID_OFFSET,MANUFACTURER_ID_LENGTH); 232 manufacturerId = getDecimalFromBinary( array + MANUFACTURER_ID_OFFSET,MANUFACTURER_ID_LENGTH); 233 recurseDecimal(manufacturerId); 234 #endif 235 236 #ifdef Split_Tags_With 237 USART_Transmit(Split_Tags_With); 238 #endif 239 240 #ifdef Site_Code_Output 241 242 int siteCode = getDecimalFromBinary( array + SITE_CODE_OFFSET,SITE_CODE_LENGTH); 243 recurseDecimal(siteCode); 244 #endif 245 246 #ifdef Split_Tags_With 247 USART_Transmit(Split_Tags_With); 248 #endif 249 250 #ifdef Unique_Id_Output 251 int lastId = getDecimalFromBinary( array + UNIQUE_ID_OFFSET,UNIQUE_ID_LENGTH); 252 recurseDecimal(lastId); 253 #endif 254 255 USART_Transmit(' '); 256 USART_Transmit(' '); 257 } 258 void printHexadecimal (int array[45]) { 259 int i; 260 #ifdef Manufacturer_ID_Output 261 for (i = MANUFACTURER_ID_OFFSET; i < MANUFACTURER_ID_OFFSET+MANUFACTURER_ID_LENGTH; i+=4) { 262 USART_Transmit(binaryTohex(array[i],array[i+1],array[i+2],array[i+3])); 263 } 264 #endif 265 266 #ifdef Split_Tags_With 267 USART_Transmit(Split_Tags_With); 268 #endif 269 270 #ifdef Site_Code_Output 271 for (i = SITE_CODE_OFFSET; i < SITE_CODE_OFFSET+SITE_CODE_LENGTH; i+=4) { 272 USART_Transmit(binaryTohex(array[i],array[i+1],array[i+2],array[i+3])); 273 } 274 #endif 275 276 #ifdef Split_Tags_With 277 USART_Transmit(Split_Tags_With); 278 #endif 279 280 #ifdef Unique_Id_Output 281 for (i = UNIQUE_ID_OFFSET; i < UNIQUE_ID_OFFSET+UNIQUE_ID_LENGTH; i+=4) { 282 USART_Transmit(binaryTohex(array[i],array[i+1],array[i+2],array[i+3])); 283 } 284 #endif 285 USART_Transmit(' '); 286 USART_Transmit(' '); 287 } 288 289 290 291 void printBinary (int array[45]) { 292 int i; 293 #ifdef Manufacturer_ID_Output 294 for (i = MANUFACTURER_ID_OFFSET; i < MANUFACTURER_ID_OFFSET+MANUFACTURER_ID_LENGTH; i++) { 295 USART_Transmit('0'+array[i]); 296 } 297 #endif 298 299 #ifdef Split_Tags_With 300 USART_Transmit(Split_Tags_With); 301 #endif 302 303 #ifdef Site_Code_Output 304 for (i = SITE_CODE_OFFSET; i < SITE_CODE_OFFSET+SITE_CODE_LENGTH; i++) { 305 USART_Transmit('0'+array[i]); 306 } 307 #endif 308 309 #ifdef Split_Tags_With 310 USART_Transmit(Split_Tags_With); 311 #endif 312 313 #ifdef Unique_Id_Output 314 for (i = UNIQUE_ID_OFFSET; i < UNIQUE_ID_OFFSET+UNIQUE_ID_LENGTH; i++) { 315 USART_Transmit('0'+array[i]); 316 } 317 #endif 318 USART_Transmit(' '); 319 USART_Transmit(' '); 320 } 321 322 323 324 325 /********************************* Search Tag ********************************* 326 | This function searches for a tag in the list of tags stored in the flash | 327 | memory, if the tag is found then the function returns 1 (true) if the tag | 328 | is not found then the function returns 0 (false) | 329 ******************************************************************************/ 330 int searchTag (int tag) { 331 int i; 332 for (i = 0; i < namesize; i++) { 333 if (tag == names[i]) { 334 return 1; 335 } 336 } 337 return 0; 338 } 339 340 341 342 343 void whiteListSuccess () { 344 PORTB |= 0x04; 345 // open the door 346 OCR1A = 10000 - SERVO_OPEN; 347 { 348 unsigned long i; 349 for (i = 0; i < 2500000; i++) { 350 if (!((PINB & (1<<7))>>7)) { 351 break; 352 } 353 } 354 } 355 //close the door 356 OCR1A = 10000 - SERVO_CLOSE; 357 { 358 unsigned long i; 359 for (i = 0; i < 500000; i++) { 360 asm volatile ("nop"); 361 } 362 } 363 OCR1A = 0; 364 wait (5000); 365 } 366 void whiteListFailure () { 367 PORTB |= 0x08; 368 wait (5000); 369 } 370 371 372 373 ////////////////////////////////////////////////////////////////////////////// 374 ///////////////////////////// ANALYSIS FUNCTIONS ///////////////////////////// 375 ////////////////////////////////////////////////////////////////////////////// 376 377 /************************* CONVERT RAW DATA TO BINARY ************************* 378 | Converts the raw 'pulse per wave' count (5,6,or 7) to binary data (0, or 1) | 379 ******************************************************************************/ 380 void convertRawDataToBinary (char * buffer) { 381 int i; 382 for (i = 1; i < ARRAYSIZE; i++) { 383 if (buffer[i] == 5) { 384 buffer[i] = 0; 385 } 386 else if (buffer[i] == 7) { 387 buffer[i] = 1; 388 } 389 else if (buffer[i] == 6) { 390 buffer[i] = buffer[i-1]; 391 } 392 else { 393 buffer[i] = -2; 394 } 395 } 396 } 397 398 /******************************* FIND START TAG ******************************* 399 | This function goes through the buffer and tries to find a group of fifteen | 400 | or more 1's in a row. This sigifies the start tag. If you took the fifteen | 401 | ones in multibit they would come out to be '111' in single-bit | 402 ******************************************************************************/ 403 int findStartTag (char * buffer) { 404 int i; 405 int inARow = 0; 406 int lastVal = 0; 407 for (i = 0; i < ARRAYSIZE; i++) { 408 if (buffer [i] == lastVal) { 409 inARow++; 410 } 411 else { 412 // End of the group of bits with the same value 413 if (inARow >= 15 && lastVal == 1) { 414 // Start tag found 415 break; 416 } 417 // group of bits was not a start tag, search next tag 418 inARow = 1; 419 lastVal = buffer[i]; 420 } 421 } 422 return i; 423 } 424 425 /************************ PARSE MULTIBIT TO SINGLE BIT ************************ 426 | This function takes in the start tag and starts parsing the multi-bit code | 427 | to produce the single bit result in the outputBuffer array the resulting | 428 | code is single bit manchester code | 429 ******************************************************************************/ 430 void parseMultiBitToSingleBit (char * buffer, int startOffset, int outputBuffer[]) { 431 int i = startOffset; // the offset value of the start tag 432 int lastVal = 0; // what was the value of the last bit 433 int inARow = 0; // how many identical bits are in a row// this may need to be 1 but seems to work fine 434 int resultArray_index = 0; 435 for (;i < ARRAYSIZE; i++) { 436 if (buffer [i] == lastVal) { 437 inARow++; 438 } 439 else { 440 // End of the group of bits with the same value 441 if (inARow >= 4 && inARow <= 8) { 442 // there are between 4 and 8 bits of the same value in a row 443 // Add one bit to the resulting array 444 outputBuffer[resultArray_index] = lastVal; 445 resultArray_index += 1; 446 } 447 else if (inARow >= 9 && inARow <= 14) { 448 // there are between 9 and 14 bits of the same value in a row 449 // Add two bits to the resulting array 450 outputBuffer[resultArray_index] = lastVal; 451 outputBuffer[resultArray_index+1] = lastVal; 452 resultArray_index += 2; 453 } 454 else if (inARow >= 15 && lastVal == 0) { 455 // there are more then 15 identical bits in a row, and they are 0s 456 // this is an end tag 457 break; 458 } 459 // group of bits was not the end tag, continue parsing data 460 inARow = 1; 461 lastVal = buffer[i]; 462 if (resultArray_index >= 90) { 463 //return; 464 } 465 } 466 } 467 } 468 469 /******************************* Analize Input ******************************* 470 | analizeInput(void) parses through the global variable and gets the 45 bit | 471 | id tag. | 472 | 1) Converts raw pulse per wave count (5,6,7) to binary data (0,1) | 473 | 2) Finds a start tag in the code | 474 | 3) Parses the data from multibit code (11111000000000000111111111100000) to | 475 | singlebit manchester code (100110) untill it finds an end tag | 476 | 4) Converts manchester code (100110) to binary code (010) | 477 *****************************************************************************/ 478 void analizeInput (void) { 479 int i; // Generic for loop 'i' counter 480 int resultArray[90]; // Parsed Bit code in manchester 481 int finalArray[45]; //Parsed Bit Code out of manchester 482 int finalArray_index = 0; 483 484 // Initilize the arrays so that any errors or unchanged values show up as 2s 485 for (i = 0; i < 90; i ++) { resultArray[i] = 2; } 486 for (i = 0; i < 45; i++) { finalArray[i] = 2; } 487 488 // Convert raw data to binary 489 convertRawDataToBinary (begin); 490 491 // Find Start Tag 492 int startOffset = findStartTag(begin); 493 PORTB |= 0x10; // turn an led on on pin B5) 494 495 // Parse multibit data to single bit data 496 parseMultiBitToSingleBit(begin, startOffset, resultArray); 497 498 // Error checking, see if there are any unset elements of the array 499 for (i = 0; i < 88; i++) { // ignore the parody bit ([88] and [89]) 500 if (resultArray[i] == 2) { 501 return; 502 } 503 } 504 //------------------------------------------ 505 // MANCHESTER DECODING 506 //------------------------------------------ 507 for (i = 0; i < 88; i+=2) { // ignore the parody bit ([88][89]) 508 if (resultArray[i] == 1 && resultArray[i+1] == 0) { 509 finalArray[finalArray_index] = 1; 510 } 511 else if (resultArray[i] == 0 && resultArray[i+1] == 1) { 512 finalArray[finalArray_index] = 0; 513 } 514 else { 515 // The read code is not in manchester, ignore this read tag and try again 516 // free the allocated memory and end the function 517 return; 518 } 519 finalArray_index++; 520 } 521 522 #ifdef Binary_Tag_Output // Outputs the Read tag in binary over serial 523 printBinary (finalArray); 524 #endif 525 526 #ifdef Hexadecimal_Tag_Output // Outputs the read tag in Hexadecimal over serial 527 printHexadecimal (finalArray); 528 #endif 529 530 #ifdef Decimal_Tag_Output 531 printDecimal (finalArray); 532 #endif 533 534 535 #ifdef Whitelist_Enabled 536 if (searchTag(getDecimalFromBinary(finalArray+UNIQUE_ID_OFFSET,UNIQUE_ID_LENGTH))){ 537 whiteListSuccess (); 538 } 539 else { 540 whiteListFailure(); 541 } 542 #endif 543 } 544 545 /******************************* MAIN FUNCTION ******************************* 546 | This is the main function, it initilized the variabls and then waits for | 547 | interrupt to fill the buffer before analizing the gathered data | 548 *****************************************************************************/ 549 int main (void) { 550 int i = 0; 551 552 //------------------------------------------ 553 // VARIABLE INITLILIZATION 554 //------------------------------------------ 555 556 // Load the list of valid ID tags 557 addNames(); 558 559 //==========> PIN INITILIZATION <==========// 560 DDRD = 0x00; // 00000000 configure output on port D 561 DDRB = 0x1E; // 00011100 configure output on port B 562 563 //=========> SERVO INITILIZATION <=========// 564 ICR1 = 10000;// TOP count for the PWM TIMER 565 566 // Set on match, clear on TOP 567 TCCR1A = ((1 << COM1A1) | (1 << COM1A0)); 568 TCCR1B = ((1 << CS11) | (1 << WGM13)); 569 570 // Move the servo to close Position 571 OCR1A = 10000 - SERVO_CLOSE; 572 { 573 unsigned long j; 574 for (j = 0; j < 500000; j++) { 575 asm volatile ("nop"); 576 } 577 } 578 // Set servo to idle 579 OCR1A = 0; 580 581 // USART INITILIZATION 582 USART_Init(); 583 584 //========> VARIABLE INITILIZATION <=======// 585 count = 0; 586 begin = malloc (sizeof(char)*ARRAYSIZE); 587 iter = 0; 588 for (i = 0; i < ARRAYSIZE; i ++) { 589 begin[i] = 0; 590 } 591 592 //=======> INTERRUPT INITILAIZATION <======// 593 sei (); // enable global interrupts 594 EICRA = 0x03; // configure interupt INT0 595 EIMSK = 0x01; // enabe interrupt INT0 596 597 //------------------------------------------ 598 // MAIN LOOP 599 //------------------------------------------ 600 while (1) { 601 sei(); //enable interrupts 602 603 while (1) { // while the card is being read 604 if (iter >= ARRAYSIZE) { // if the buffer is full 605 cli(); // disable interrupts 606 break; // continue to analize the buffer 607 } 608 } 609 610 PORTB &= ~0x1C; 611 612 //analize the array of input 613 analizeInput (); 614 615 //reset the saved values to prevent errors when reading another card 616 count = 0; 617 iter = 0; 618 for (i = 0; i < ARRAYSIZE; i ++) { 619 begin[i] = 0; 620 } 621 } 622 }
There are two distinct cycle lengths in the detected input signal, "long" and "short",
corresponding to logical ones and zeroes, respectively.
A binary stream of stretches of repeated ones and zeroes is assembled,
and then decimated into the original coded bits on the RFID tag, after decoding the Manchester encoding.
Here is the actual code:
1 /* Arduino program for DIY FSK RFID Reader 2 * See description and circuit diagram at http://playground.arduino.cc/Main/DIYRFIDReader 3 * Tested on Arduino Nano and several FSK RFID tags 4 * Hardware/Software design is based on and derived from: 5 * Arduino/Timer1 library example 6 * June 2008 | jesse dot tane at gmail dot com 7 * AsherGlick: / AVRFID https://github.com/AsherGlick/AVRFID 8 * Micah Dowty: 9 * http://forums.parallax.com/showthread.php?105889-World-s-simplest-RFID-reader 10 * 11 * Copyright (C) 2011 by edude/Arduino Forum 12 13 This program is free software: you can redistribute it and/or modify 14 it under the terms of the GNU General Public License as published by 15 the Free Software Foundation, either version 3 of the License, or 16 (at your option) any later version. 17 18 This program is distributed in the hope that it will be useful, 19 but WITHOUT ANY WARRANTY; without even the implied warranty of 20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 GNU General Public License for more details. 22 23 You should have received a copy of the GNU General Public License 24 along with this program. If not, see <http://www.gnu.org/licenses/>. 25 26 */ 27 28 #include "TimerOne.h" 29 30 int ledPin = 13; // LED connected to digital pin 13 31 int inPin = 7; // sensing digital pin 7 32 int val; 33 int bitlenctr = 0; 34 int curState = 0; 35 36 #define maxBuf 1000 //reduce to 100 or so for debugging 37 #define debug 0 38 39 char raw[maxBuf]; 40 41 int index = 0; 42 int bufnum = 0; 43 #define redLED 12 44 #define grnLED 11 45 46 void setup() 47 { 48 Serial.begin(9600); 49 Timer1.initialize(7); // initialize timer1, and set the frequency; this drives both the LC tank as well as the pulse timing clock 50 // note: modify this as needed to achieve resonance and good match with the desired tags 51 // the argument value is in microseconds per RF cycle, so 8us will yield RF of 125kHz, 7us --> 143kHz, etc. 52 53 Timer1.pwm(9, 512); // setup pwm on pin 9, 50% duty cycle 54 Timer1.attachInterrupt(callback); // attaches callback() as a timer overflow interrupt, once per RF cycle 55 56 pinMode(ledPin, OUTPUT); // sets the digital pin 13 as output for scope monitoring 57 pinMode(inPin, INPUT); // sets the digital pin 7 as input to sense receiver input signal 58 pinMode(grnLED, OUTPUT); 59 pinMode(redLED, OUTPUT); 60 digitalWrite(grnLED, 0); 61 digitalWrite(redLED, 1); 62 } 63 64 void callback() 65 { 66 val = digitalRead(inPin); 67 digitalWrite(ledPin, val); // for monitoring 68 bitlenctr++; 69 if(val != curState) { 70 // got a transition 71 curState = val; 72 if(val == 1) { 73 // got a start of cycle (low to high transition) 74 if(index < maxBuf) { 75 raw[index++] = bitlenctr; 76 } 77 bitlenctr = 1; 78 } 79 } 80 } 81 82 void loop() 83 { 84 if(index >= maxBuf) { 85 86 Serial.print("got buf num: "); 87 Serial.println(bufnum); 88 89 if(debug) { 90 for(int i = 0; i < maxBuf; 91 i++) { 92 Serial.print((int)raw[i]); 93 Serial.print("/"); 94 } 95 Serial.println("///raw data"); 96 delay(2000); 97 } 98 99 // analyze this buffer 100 // first convert pulse durations into raw bits 101 int tot1 = 0; 102 int tot0 = 0; 103 int tote = 0; 104 int totp = 0; 105 raw[0] = 0; 106 for(int i = 1; i < maxBuf; i++) { 107 int v = raw[i]; 108 if(v == 4) { 109 raw[i] = 0; 110 tot0++; 111 } 112 else if(v == 5) { 113 raw[i] = raw[i - 1]; 114 totp++; 115 } 116 else if(v == 6 || v == 7) { 117 raw[i] = 1; 118 tot1++; 119 } 120 else { 121 raw[i] = 101; // error code 122 tote++; 123 } 124 } 125 126 // next, search for a "start tag" of 15 high bits in a row 127 int samecnt = 0; 128 int start = -1; 129 int lastv = 0; 130 for(int i = 0; i < maxBuf; i++) { 131 if(raw[i] == lastv) { 132 // inside one same bit pattern, keep scanning 133 samecnt++; 134 } 135 else { 136 // got new bit pattern 137 if(samecnt >= 15 && lastv == 1) { 138 // got a start tag prefix, record index and exit 139 start = i; 140 break; 141 } 142 // either group of 0s, or fewer than 15 1s, so not a valid tag, keep scanning 143 samecnt = 1; 144 lastv = raw[i]; 145 } 146 } 147 148 // if a valid prefix tag was found, process the buffer 149 if(start > 0 && start < (maxBuf - 5*90)) { //adjust to allow room for full dataset past start point 150 process_buf(start); 151 } 152 else { 153 Serial.println("no valid data found in buffer"); 154 } 155 if(debug) { 156 for(int i = 0; i < maxBuf; 157 i++) { 158 Serial.print((int)raw[i]); 159 Serial.print("/"); 160 } 161 Serial.print("/// buffer stats: zeroes:"); 162 Serial.print(tot0); 163 Serial.print("/ones:"); 164 Serial.print(tot1); 165 Serial.print("/prevs:"); 166 Serial.print(totp); 167 Serial.print("/errs:"); 168 Serial.println(tote); 169 delay(1000); 170 } 171 172 // start new buffer, reset all parameters 173 bufnum++; 174 curState = 0; 175 index = 0; 176 } 177 else { 178 delay(5); 179 } 180 } 181 182 // process an input buffer with a valid start tag 183 // start argument is index to first 0 bit past prefix tag of 15+ ones 184 void process_buf(int start) { 185 // first convert multi bit codes (11111100000...) into manchester bit codes 186 int lastv = 0; 187 int samecnt = 0; 188 char manch[91]; 189 char final[45]; 190 int manchindex = 0; 191 192 Serial.println("got a valid prefix, processing data buffer..."); 193 for(int i = start + 1; i < maxBuf && manchindex < 90; i++) { 194 if(raw[i] == lastv) { 195 samecnt++; 196 } 197 else { 198 // got a new bit value, process the last group 199 if(samecnt >= 3 && samecnt <= 8) { 200 manch[manchindex++] = lastv; 201 } 202 else if(samecnt >= 9 && samecnt <= 14) { 203 // assume a double bit, so record as two separate bits 204 manch[manchindex++] = lastv; 205 manch[manchindex++] = lastv; 206 } 207 else if(samecnt >= 15 && lastv == 0) { 208 Serial.println("got end tag"); 209 // got an end tag, exit 210 break; 211 } 212 else { 213 // last bit group was either too long or too short 214 Serial.print("****got bad bit pattern in buffer, count: "); 215 Serial.print(samecnt); 216 Serial.print(", value: "); 217 Serial.println(lastv); 218 err_flash(3); 219 return; 220 } 221 samecnt = 1; 222 lastv = raw[i]; 223 } //new bit pattern 224 } 225 226 Serial.println("converting manchester code to binary..."); 227 // got manchester version, convert to final bits 228 for(int i = 0, findex = 0; i < 90; i += 2, findex++) { 229 if(manch[i] == 1 && manch[i+1] == 0) { 230 final[findex] = 1; 231 } 232 else if(manch[i] == 0 && manch[i+1] == 1) { 233 final[findex] = 0; 234 } 235 else { 236 // invalid manchester code, exit 237 Serial.println("****got invalid manchester code"); 238 err_flash(3); 239 return; 240 } 241 } 242 243 // convert bits 28 thru 28+16 into a 16 bit integer 244 int code = 0; 245 int par = 0; 246 for(int i = 28, k = 15; i < 28+16; i++, k--) { 247 code |= (int)final[i] << k; 248 } 249 int paritybit = final[28+16]; 250 for(int i = 0; i < 45; i++) { 251 par ^= final[i]; 252 } 253 254 if(par) { 255 Serial.print("got valid code: "); 256 Serial.println((unsigned int)code); 257 // do something here with the detected code... 258 // 259 // 260 digitalWrite(redLED, 0); 261 digitalWrite(grnLED, 1); 262 delay(2000); 263 digitalWrite(grnLED, 0); 264 digitalWrite(redLED, 1); 265 } 266 else { 267 Serial.println("****parity error for retrieved code"); 268 err_flash(3); 269 } 270 } 271 272 // flash red for duration seconds 273 void err_flash(int duration) { 274 return; 275 for(int i = 0; i < duration*10; i++) { 276 digitalWrite(redLED, 0); 277 delay(50); 278 digitalWrite(redLED, 1); 279 delay(50); 280 } 281 }
Status
The device and transceiver antenna have been built and tested on multiple FSK RFID tags of various kinds,
in breadboard and soldered perfboard versions, connected to remote and local probes.
When the probe is properly tuned, the device can reliably detect FSK RFID tags within a range of 0 to at least 2 inches from the coil,
although it may be possible that this can be extended with larger coil sizes and/or other optimizations.
The circuit has also been simulated on Spice, as described below.
As seen in the LTspiceIV screenshot above, the circuit (with a passive virtual ground reference - see note below)
was simulated on a computer, and the results confirmed the essential design,
closely replicating the waveforms actually seen on the oscilloscope.
The RFID transponder tag was simulated as a coupled transformer winding with a resonantly tuned capacitor,
shunted to ground by a square-wave signal.
The RFID tag's ground is connected to the main circuit's ground for simulation purposes.
The inductive coupling between the two "transformer windings" is a variable which can be changed in LTspice,
and was varied for testing between 1 and 0.01 (0.015 is shown in the waveforms above),
equivalent to having the RFID tag positioned at different distances from the reader coil.
Notes
The Vcc/2 virtual ground voltage for IC1's non-inverting input
can also be taken directly from the midpoint of the 100K voltage divider resistors,
bypassing the second opamp.
In such a case, the divider's midpoint should be connected to pin3 of IC1 via a 1M resistor.
Arduino RFID reader and pin current sourcing
http://electronics.stackexchange.com/questions/82374/arduino-rfid-reader-and-pin-current-sourcing
I'm thinking about building the DIY RFID reader described here
http://playground.arduino.cc/Main/DIYRFIDReader, with an arduino uno.
For the LC filter, I have a 330uH inductor at home
(and I'm using a 4.7nF capacitor for C1 instead of the 7nF shown on the schematic, to get at 125khz resonant frequency)
and also I'm using 1N4148 diodes instead of 1N914 (I couldn't find them on stock at the local electronics shop).
I did the simulation of the circuit in LT Spice (schematic is quite similar to what the original author has)
and it shows we that the current draw from the arduino pin D9 goes up to 400 mA,
from what I know the AVR can't source more than 50 mA per pin.
Here's the LTSpice circuit:
And the simulation result:
My question is: what will happen in reality when building the circuit,
will the pin source less current than the simulation and it will simply work
or I run the risk of damaging the pin by attempting to source that much current
(perhaps not instant damage, but long term) ?
If there is risk for damaging the pin, what solutions do I have to prevent it?
Would a simple current limiting resistor do?
Would it be better to use a push-pull configuration like the one below ?
Firstly, the 330uH inductor you already have is almost certainly not suitable
unless it is an air cored inductor wound on some kind of inert magnetic material
or an open ended ferrite rod of reasonably low permeability.
Check what it says on the circuit you linked to.
This coil has to transmit and receive so it needs to be likely a non-bought-in (and likely hand-wound) part.
D9 excessive current in simulation - you need to current limit the top circuit because it's driving a series resonant coil
and capacitor and this will act like a short circuit at resonance. Maybe try 100 ohms or a bit less.
In your alternative oscillator circuit you've got Q3 upside down in your simulation of the RFID transmitter -
that will cause excessive base currents through Q3 and may easily account for the excessive current from IO D9.
Your tag circuit might also benefit from a 100 ohm in series with the collector of Q1 too.
What does the waveform look like when the tag isn't responding - does it still show amplitude modulation issues?
You are right, the original circuit suggests a hand-wound part, I thought I could use the 330uH filter inductor ...
It's not clear to me exactly what the difference would be between the inductor I have and a hand-wound inductor.
Would a 220 ohm resistor in series with D9 (not shown in the original circuit) +
the hand-wound inductor (as described in the original circuit) work?
As for the alternative circuit, you are right about Q3 (I did the picture in a hurry, next time I'll pay more attention).
SIMPLEST RFID READER?
http://scanlime.org/2008/08/simplest-rfid-reader/
That’s a Propeller microcontroller board with a few resistors and capacitors on it.
Just add a coil of wire, and you have an RFID reader.
Here’s a picture of it scanning my corporate ID badge,
and displaying the badge’s 512 bits of content on a portable TV screen:
I’ve been interested in building an RFID reader for a crazy project a friend of mine is working on
(automated beer dispensing system and it was an excuse to do some analog tinkering,
so I had a go at building an RFID reader with the Propeller.
Why not use Parallax’s fine RFID reader accessory?
I wanted to be able to read any off-the-shelf card, including common proximity ID badges.
I just got it working. I’d love to be proved wrong,
but as far as I know this is the world’s simplest RFID reader design
All capacitances are in picofarads. C1 and C3 should be 1000 pF, not 1000 nF. Likewise, C2 is 2200 pF.
Yep.. just a propeller and a few passive components.
To understand this circuit, it’s helpful to know how RFID works first.
Here’s the 10-second RFID primer for those who aren’t already familiar with it:
RFID tags work via magnetic fields, like a transformer.
A coil in the reader generates a magnetic carrier wave, which is picked up by a coil in the tag or card.
This carrier wave powers the card, and provides it with a clock reference.
To send data, the card varies the amount of current it draws from the reader’s field,
attenuating the carrier wave slightly.
This attenuation is usually used to send some kind of modulated signal.
The card I’ve been testing with uses a 125 kHz carrier wave,
and FSK (Frequency Shift Keyed) modulation.
Zeroes and ones are attenuation waveforms with different frequencies.
So, step one: generating a carrier wave with the propeller.
I use a counter, naturally, and I generate a differential-drive signal on two pins.
This gives me 125 kHz at about 6.6V peak-to-peak.
I use an LC tank tuned near 125 kHz to amplify this and shape it into a sine wave.
Here’s my carrier wave, measured at the junction between L1 and C1.
Note the scale- it’s about 20 volts peak-to-peak! (Also notice that my tuning isn’t quite perfect. Sadness and despair!)
When you bring a proximity card near the reader coil, you can see the attenuation waveform overlaying the carrier wave.
This is at the same voltage scale, measured at the same point, but I zoomed out on the X axis so you can see the pattern clearly:
To detect this signal, I use D1, C2, and R1 as a peak detector and low-pass filter,
to remove the majority of the high-voltage carrier wave.
This is then AC coupled via C3.
At this point, the left side of C3 has a “floating” low-voltage waveform
that I can position anywhere I want, by changing the bias on C3.
To generate this bias voltage, I use CTRB in DUTY mode.
I’ll explain why I call this pin “threshold” later.
R4 and C4 are a low-pass filter give me an analog output voltage from CTRB.
This signal is usually DC- it’s used for calibration.
R3 “pulls” the floating waveform toward the analog value output by CTRB.
Now I take advantage of the Propeller’s Vdd/2 input threshold, and use the “input” pin as a comparator.
(R2 is just to limit high-frequency noise.)
Now I can use the CTRB voltage to adjust the detection threshold- how much attenuation causes “input” to read 0 vs 1.
Now I have a digital signal, but it’s still really noisy.
I use an assembly-language cog to reject as much noise as possible,
and time the resulting FSK pulses.
This image shows the analog signal at the junction between R2 and C3, along with the digital pulse detector’s output.
The amount of time between pulses signifies a “1″ or “0″ bit.
In this case, the detection threshold is at 9 carrier wave cycles.
Less than 9, it’s a 0. More than 9, it’s a 1 bit.
End result: I can display the 512 bits of data from my corporate proximity badge. Neat.
I’d include more details on the actual data I’ve captured and the protocol,
but so far I’ve only been able to test this with a couple ID badges from the office and I’d rather not share those bits
I have a Parallax RFID starter kit on order, so I’ll let you know if I can read any of those tags successfully.
Source code is attached, but be warned it’s hugely messy.
Update: The latest code and schematics are now have a home in the Object Exchange:
http://obex.parallax.com/object/549.
1 { 2 3 rfid-lf - Minimalist software-only reader for low-frequency RFID tags 4 ───────────────────────────────────────────────────────────────────── 5 6 I. Supported Tags 7 8 Tested with the EM4102 compatible RFID tags sold by Parallax, and with 9 HID proximity cards typically issued for door security. 10 11 These are two fairly typical ASK and FSK tags, respectively. To support 12 other RFID protocols, it should be possible to use one of these two 13 decoders as a starting point. 14 15 I. Theory of operation 16 17 The Propeller itself generates the 125 kHz carrier wave, which excites 18 an LC tank. The inductor in this tank is an antenna coil, which becomes 19 electromagnetically coupled to the RFID tag when it comes into range. 20 This carrier powers the RFID tag. The RFID sends back data by modulating 21 the amplitude of this carrier. We detect this modulation via a peak 22 detector and low-pass filter (D1, C2, R1). The detected signal is AC 23 coupled via C3, and an adjustable bias is applied via R3 from a PWM signal 24 which is filtered by R4 and C4. This signal is fed directly to an input 25 pin via R2 (which limits high-frequency noise) where the pin itself acts 26 as an analog comparator. 27 28 The waveform which exits the peak detector is a sequence of narrow spikes 29 at each oscillation of the carrier wave. Each of these spikes occurs when 30 C2 charges quickly via D1, then discharges slowly via R1. We can use this 31 to fashion a simple A/D converter by having the Propeller use a counter to 32 measure the duty cycle of this waveform. The higher the carrier amplitude, 33 the more charge was stored in C2, and the longer the pulse will be. 34 35 At this point, we have a digital representation of the baseband RF signal 36 from the RFID tags. This signal is then put through a bank of demodulators, 37 filters, and protocol decoders. 38 39 We include multiple closed-loop control systems in order to keep the reader 40 calibrated. We adjust the Threshold (THR) voltage dynamically in order to 41 keep the duty cycle at a reasonable level so we don't over-saturate our 42 A/D converter. We also automatically tweak the carrier frequency in order 43 to keep the LC tank operating at its resonant peak. 44 45 This object requires two cogs, mostly just because it needs three counter 46 units: One to generate the carrier, one to measure incoming pulses, and one 47 to generate the threshold voltage. 48 49 II. Schematic 50 51 (P5) IN º─┐ (P1) C+ ©──┐ 52 │ │ 53 R2 ¼ ¶ L1 54 R4 │ C3 D1 │ 55 (P3) THR ©──½¾──┳──½¾──┻──«──┳──┳──¦§──┫ 56 │ R3 │ │ │ 57 C4¬® R1 ¼ ¬®C2 ¬®C1 58 │ 59 │ 60 (P0) C- ©──┘ 61 62 C1 1.5 nF 63 C2 2.2 nF 64 C3 1 nF 65 C4 2.2 nF 66 R1 1 MΩ 67 R2 270 Ω 68 R4 100 kΩ 69 R3 220 kΩ 70 D1 Some garden variety sigal diode from my junk drawer. 71 L1 About 1 mH. Tune for 125 kHz resonance for C1. 72 73 Optional parts: 74 75 - An amplifier for the carrier wave. Ideally it would be a very high 76 slew rate op-amp or buffer which could convert the 3.3v signal from 77 the Prop into a higher-voltage square wave for exciting the LC tank. 78 79 I've tried a MAX233A, but it was unsuccessful due to the low slew rate 80 which caused excessive harmonic distortion. The best option is probably 81 a high voltage H-bridge, but I haven't tested this yet. 82 83 - An external comparator for the input value. This makes the circuit 84 easier to debug, and it helps if you're in a noisy electrical environment. 85 86 Hook the inverting input up to a voltage divider that generates a Vdd/2 87 reference, and the non-inverting input up to the junction between C3 and R3. 88 I've tested this with a MAX473 op-amp. 89 90 III. License 91 92 ┌───────────────────────────────────┐ 93 │ Copyright (c) 2008 Micah Dowty │ 94 │ See end of file for terms of use. │ 95 └───────────────────────────────────┘ 96 97 } 98 99 CON 100 CARRIER_HZ = 125_000 ' Initial carrier frequency 101 DEFAULT_THRESHOLD = $80000000 ' Initial comparator frequency 102 THRESHOLD_GAIN = 7 ' Log2 gain for threshold control loop 103 CARRIER_RATE = 7 ' Inverse Log2 rate for carrier frequency control loop 104 CARRIER_GAIN = 300 ' Gain for carrier frequency control loop 105 106 SHIELD2_PIN = 6 ' Optional, driven to ground to shield INPUT. 107 INPUT_PIN = 5 ' Input signal 108 SHIELD1_PIN = 4 ' Optional, driven to ground to shield INPUT. 109 THRESHOLD_PIN = 3 ' PWM output for detection threshold 110 DEBUG_PIN = 2 ' Optional, for oscilloscope debugging 111 CARRIER_POS_PIN = 1 ' Carrier wave pin 112 CARRIER_NEG_PIN = 0 ' Carrier wave pin 113 114 ' Code formats. 115 ' The low 16 bits indicate length, in longs. 116 ' Other bits are used to make each format unique. 117 118 FORMAT_EM4102 = $0001_0002 ' Two longs: 8-bit manufacturer code, 32-bit unique ID. 119 FORMAT_HID = $0002_0002 ' HID 128 KHz prox cards. 45-bit code. 120 121 VAR 122 byte cog1, cog2 123 long format_buf 124 long shared_pulse_len 125 long em_buffer[2] 126 long hid_buffer[2] 127 128 PUB start | period, okay 129 130 ' Fundamental timing parameters: Default carrier wave 131 ' drive frequency, and the period of the carrier in clock 132 ' cycles. 133 134 init_frqa := fraction(CARRIER_HZ, clkfreq) 135 period := clkfreq / CARRIER_HZ 136 137 ' Derived timing parameters 138 139 pulse_target := period / 3 ' What is our 'center' pulse width? 140 next_hyst := pulse_target 141 hyst_constant := period / 100 ' Amount of pulse hysteresis 142 143 ' Output buffers 144 em_buf_ptr := @em_buffer 145 hid_buf_ptr := @hid_buffer 146 format_ptr := @format_buf 147 pulse_ptr1 := @shared_pulse_len 148 pulse_ptr2 := @shared_pulse_len 149 format_buf~ 150 151 cog1 := cognew(@cog1_entry, 0) + 1 152 okay := cog2 := cognew(@cog2_entry, 0) + 1 153 154 155 PUB stop 156 if cog2 157 cogstop(cog1~ - 1) 158 cogstop(cog2~ - 1) 159 160 PUB read(buffer) : format 161 '' Read an RFID code, if one is available. 162 '' 163 '' If a code is available, it is copied into 'buffer', and we return 164 '' a FORMAT_* constant that identifies the code's format. If no code 165 '' is available, returns zero. 166 '' 167 '' The format code's low 16 bits indicate the length of the received 168 '' RFID code, in longs. The other format bits are used to uniquely 169 '' identify each format. 170 '' 171 '' The buffer must be at least 16 longs, to hold the largest code format. 172 173 format := format_buf 174 175 if format == FORMAT_EM4102 176 longmove(buffer, @em_buffer, constant(FORMAT_EM4102 & $FFFF)) 177 178 if format == FORMAT_HID 179 longmove(buffer, @hid_buffer, constant(FORMAT_HID & $FFFF)) 180 181 format_buf~ 182 183 PRI fraction(a, b) : f 184 a <<= 1 185 repeat 32 186 f <<= 1 187 if a => b 188 a -= b 189 f++ 190 a <<= 1 191 192 DAT 193 194 '============================================================================== 195 ' Cog 1: Pulse timing 196 '============================================================================== 197 198 org 199 200 cog1_entry 201 202 ' 203 ' Measure a low pulse. 204 ' 205 ' The length of this pulse is proportional to the amplitude of the carrier. 206 ' To measure it robustly, we'll measure the average duty cycle rather than 207 ' looking at actual rising or falling edges. 208 ' 209 ' We could easily do this in cog2, but we're out of counters there. Measure 210 ' the pulse length using this cog's CTRA, and send it back to cog2 synchronously. 211 ' 212 213 mov ctra, :ctra_value 214 mov frqa, #1 215 216 :loop mov t0, phsa 217 wrlong t0, pulse_ptr1 218 :wait rdlong t0, pulse_ptr1 wz 219 if_z jmp #:loop 220 jmp #:wait 221 222 :ctra_value long (%01100 << 26) | INPUT_PIN 223 pulse_ptr1 long 0 224 t0 res 1 225 226 fit 227 228 229 DAT 230 231 '============================================================================== 232 ' Cog 2: Control loops and protocol decoding 233 '============================================================================== 234 235 '====================================================== 236 ' Initialization 237 '====================================================== 238 239 org 240 241 cog2_entry mov dira, init_dira 242 mov ctra, init_ctra ' CTRA generates the carrier wave 243 mov frqa, init_frqa 244 mov ctrb, init_ctrb ' CTRB generates a pulse threshold bias 245 mov frqb, init_frqb 246 247 248 '====================================================== 249 ' Main A/D loop 250 '====================================================== 251 252 mainLoop 253 ' 254 ' Synchronize each loop iteration with a rising edge on 255 ' the carrier wave. To avoid races when reading pulse_ptr, 256 ' we should ideally synchronize at a point 180 degrees 257 ' out of phase with the negative pulse from our analog peak 258 ' detector. 259 ' 260 261 mov prev_pulse, cur_pulse ' Remember previous pulse info 262 263 :high test h80000000, phsa wz 264 if_z jmp #:high 265 :low test h80000000, phsa wz 266 if_nz jmp #:low 267 268 rdlong cur_pulse, pulse_ptr2 ' Fetch pulse count from cog1 269 wrlong zero, pulse_ptr2 270 271 mov pulse_len, cur_pulse ' Measure length of pulse 272 sub pulse_len, prev_pulse 273 274 add pulse_count, #1 ' Global pulse counter, used below 275 276 ' 277 ' Adjust the comparator threshold in order to achieve our pulse_target, 278 ' using a linear proportional control loop. 279 ' 280 281 mov r0, pulse_target 282 sub r0, pulse_len 283 shl r0, #THRESHOLD_GAIN 284 sub frqb, r0 285 286 ' 287 ' We also want to dynamically tweak the carrier frequency, in order 288 ' to hit the resonance of our LC tank as closely as possible. The 289 ' value of frqb is actually a filtered representation of our overall 290 ' inverse carrier amplitude, so we want to adjust frqa in order to 291 ' minimize frqb. 292 ' 293 ' Since we can't adjust frqa drastically while the RFID reader is 294 ' operating, we'll make one small adjustment at a time, and decide 295 ' whether or not it was an improvement. This process eventually converges 296 ' on the correct resonant frequency, so it should be enough to keep our 297 ' circuit tuned as the analog components fluctuate slightly in value 298 ' due to temperature variations. 299 ' 300 ' This algorithm is divided into four phases, sequenced using two 301 ' bits from the pulse_count counter: 302 ' 303 ' 0. Store a reference frqb value, and increase frqa 304 ' 1. Test the frqb value. If it wasn't an improvement, decrease frqa 305 ' 2. Store a reference frqb value, and decrease frqa 306 ' 3. Test the frqb value. If it wasn't an improvement, increase frqa 307 ' 308 309 test pulse_count, carrier_mask wz 310 if_nz jmp #:skip_frqa 311 test pulse_count, carrier_bit0 wz 312 test pulse_count, carrier_bit1 wc 313 negc r0, #CARRIER_GAIN 314 if_nz mov prev_frqb, frqb 315 if_nz add frqa, r0 316 if_z cmp prev_frqb, frqb wc 317 if_z_and_c sub frqa, r0 318 :skip_frqa 319 320 ' 321 ' That takes care of all our automatic calibration tasks.. now to 322 ' receive some actual bits. Since our pulse length is proportional 323 ' to the amount of carrier attenuation, our demodulated bits (or 324 ' baseband FSK signal) are determined by the amount of pulse width 325 ' excursion from our center position. 326 ' 327 ' We don't need to measure the center, since we're actively balancing 328 ' our pulses around pulse_target. A simple bit detector would just 329 ' compare pulse_len to pulse_target. We go one step further, and 330 ' include a little hysteresis. 331 ' 332 333 cmp next_hyst, pulse_len wc 334 muxc outa, #|<DEBUG_PIN ' Output demodulated bit to debug pin 335 336 mov next_hyst, pulse_target ' Update hysteresis for the next bit 337 sumc next_hyst, hyst_constant 338 339 if_nc add baseband_s32, #1 340 if_nc add baseband_s256, #1 341 rcl baseband_reg+0, #1 wc ' Store in our baseband shift register 342 if_nc sub baseband_s32, #1 ' ... and keep a running total of the bits. 343 rcl baseband_reg+1, #1 wc 344 rcl baseband_reg+2, #1 wc 345 rcl baseband_reg+3, #1 wc 346 rcl baseband_reg+4, #1 wc 347 rcl baseband_reg+5, #1 wc 348 rcl baseband_reg+6, #1 wc 349 rcl baseband_reg+7, #1 wc 350 if_nc sub baseband_s256, #1 351 352 ' 353 ' Our work here is done. Give each card-specific protocol decoder 354 ' a chance to find actual ones and zeroes. 355 ' 356 357 call #rx_hid 358 call #rx_em4102 359 360 jmp #mainLoop 361 362 363 '====================================================== 364 ' EM4102 Decoder 365 '====================================================== 366 367 ' The EM4102 chip actually supports multiple clock rates 368 ' and multiple encoding schemes: Manchester, Biphase, and PSK. 369 ' Their "standard" scheme, and the one Parallax uses, is 370 ' ASK with Manchester encoding at 32 clocks per code (64 371 ' clocks per bit). Our support for this format is hardcoded. 372 ' 373 ' The EM4102's data packet consists of 40 payload bits (an 8-bit 374 ' manufacturer ID and 32-bit serial number), 9 header bits, 1 stop 375 ' bit, and 14 parity bits. This is a total of 64 bits. These bits 376 ' are manchester encoded into 128 baseband bits. 377 ' 378 ' We could decode this the traditional way- do clock/data recovery 379 ' on the Manchester signal using a DPLL, look for the header, do 380 ' manchester decoding on the rest of the packet, etc. But this is 381 ' software, and we can throw memory and CPU at the problem in order 382 ' to get a more noise-resistant decoding. 383 ' 384 ' A packet in its entirety is 4096 clocks. This is 128 manchester 385 ' codes by 32 clocks per code. We can treat this as 32 possible phases 386 ' and 128 possible code alignments. In fact, it's a more convenient 387 ' to treat it as 64 possible phases and 64 possible bits. We get the 388 ' same result, and it's less data to move around. 389 ' 390 ' To save memory, we'll decimate the signal and examine it only every 391 ' other carrier cycle. This gives us only 32 possible phases. 392 ' 393 ' Every time a code arrives, we shift it into a 64-bit shift register. 394 ' We have 32 total shift registers, which we cycle through. Every time 395 ' we shift in a new bit, we examine the entire shift register and test 396 ' whether it's a valid packet. 397 398 rx_em4102 399 test pulse_count, #1 wz ' Decimate x2 (Opposite phase from the HID decoder) 400 if_nz jmp #rx_em4102_ret 401 402 ' Low pass filter with automatic gain control. Look at an average of the last 403 ' 32 bits, and correct our duty cycle to 50% by picking a threshold based on 404 ' the average of the last 256 bits. 405 406 mov r0, baseband_s256 407 shr r0, #3 408 cmp baseband_s32, r0 wc 409 410 :shift1 rcl em_bits+1, #1 wc ' Shift in the new filtered bit 411 :shift2 rcl em_bits+0, #1 412 :load1 mov em_shift+0, em_bits+0 ' And save a copy in a static location 413 :load2 mov em_shift+1, em_bits+1 414 415 add :shift1, dest_2 ' Increment em_bits pointers 416 add :shift2, dest_2 417 add :load1, #2 418 add :load2, #2 419 cmp :shift1, em_shift1_end wz 420 if_z sub :shift1, dest_64 ' Wrap around 421 if_z sub :shift2, dest_64 422 if_z sub :load1, #64 423 if_z sub :load2, #64 424 425 rdlong r0, format_ptr wz ' Make sure the output buffer is available 426 if_nz jmp #rx_em4102_ret 427 428 ' 429 ' At this point, the encoded packet should have the following format: 430 ' (Even bits in the manchester code are not shown.) 431 ' 432 ' bits+0: 11111111_1ddddPdd_ddPddddP_ddddPddd 433 ' bits+1: dPddddPd_dddPdddd_PddddPdd_ddPPPPP0 434 ' 435 ' Where 'd' is a data bit and 'P' is a parity bit. 436 ' 437 438 mov r0, em_shift+0 ' Look for the header of nine "1" bits 439 shr r0, #32-9 440 cmp r0, #$1FF wz 441 if_nz jmp #rx_em4102_ret 442 443 rcr em_shift+1, #1 nr,wc ' Look for a footer of one "0" 444 if_c jmp #rx_em4102_ret 445 446 ' Looking good so far. Now loop over the 10 data rows... 447 448 mov em_decoded, #0 449 mov em_decoded+1, #0 450 mov em_parity, #0 451 mov r0, #10 452 :row 453 mov r2, em_shift+0 ' Extract the next row's 5 bits 454 shr r2, #18 455 and r2, #%11111 wc ' Check row parity 456 if_c jmp #rx_em4102_ret 457 458 mov r1, em_decoded+1 ' 64-bit left shift by 4 459 shl em_decoded+1, #4 460 shl em_decoded+0, #4 461 shr r1, #32-4 462 or em_decoded+0, r1 463 464 shr r2, #1 ' Drop row parity bit 465 xor em_parity, r2 ' Update column parity 466 or em_decoded+1, r2 ' Store 4 decoded bits 467 468 mov r1, em_shift+1 ' 64-bit left shift by 5 469 shl em_shift+1, #5 470 shl em_shift+0, #5 471 shr r1, #32-5 472 or em_shift+0, r1 473 474 djnz r0, #:row 475 476 mov r2, em_shift+0 ' Extract the next 4 bits 477 shr r2, #19 478 and r2, #%1111 479 xor em_parity, r2 wc ' Test column parity 480 if_c jmp #rx_em4102_ret 481 482 mov r0, em_buf_ptr ' Write the result 483 wrlong em_decoded+0, em_buf_ptr 484 add r0, #4 485 wrlong em_decoded+1, r0 486 wrlong c_format_em, format_ptr 487 488 rx_em4102_ret ret 489 490 em_shift1_end rcl em_bits+1+64, #1 wc ' This is ":shift1" above, after we've 491 ' passed the end of the em_bits array. 492 493 494 '====================================================== 495 ' HID Decoder 496 '====================================================== 497 498 ' This is a data decoder for HID's 125 kHz access control cards. 499 ' I don't have any actual documentation from HID- this is all 500 ' gleaned from public documentation of other RFID systems, and 501 ' from my own reverse engineering. 502 ' 503 ' These cards use a FSK scheme, which appears to be identical 504 ' to the one used by the Microchip MCRF200. Zeroes and ones are 505 ' nominally encoded by attenuation pulses of 8 and 10 cycles, 506 ' respectively. Bits appear to last 50 RF cycles, which is one 507 ' of the configurable bit lengths for the MCRF200. 508 ' 509 ' See Figure 2-2 of the Microchip microID 125 kHz RFID System Design Guide: 510 ' http://ww1.microchip.com/downloads/en/devicedoc/51115F.pdf 511 ' 512 ' So, to decode this signal we use a similar low-pass filter and shift 513 ' register technique as the one we used above in the EM4102 decoder, 514 ' but only after doing an FSK detection stage. 515 ' 516 ' After FSK detection and low-pass filtering, the signal appears to 517 ' be a repeating pattern of 96 of these 50-cycle bits. Each repetition 518 ' begins with the special sequence '000111'. The packet data is manchester 519 ' encoded, so runs of three zeroes or ones cannot occur during the packet body. 520 ' This means there should be 45 bits of actual packet data after manchester 521 ' decoding. 522 ' 523 ' XXX: There is almost certainly another level of encoding present, 524 ' since HID claims that these cards have either a 26-bit or 34-bit 525 ' code. It's likely that there is an error correcting/detecting code 526 ' embedded within the 45-bit signal we're receiving here. 527 528 rx_hid 529 ' This is the FSK decoder. We shift zeroes into hid_fsk_reg on every 530 ' carrier wave cycle. When we detect a stable rising edge on the baseband, 531 ' the whole hid_fsk_reg is inverted. At this point, the most recent FSK 532 ' cycle will be all ones, and the previous cycle will be all zeroes. 533 ' 534 ' On each stable rising edge, we can detect a 1 or a 0 by tapping the 535 ' hid_fsk_reg. The location of the tap determines our threshold frequency. 536 ' This detected bit is latched until the next rising edge. 537 538 shl hid_fsk_reg, #1 ' Advance the FSK detection shifter 539 540 mov r0, baseband_reg ' Detect rising edges 541 and r0, #%1111 542 cmp r0, #%0001 wz ' If Z=1, it's an edge. 543 544 if_z xor hid_fsk_reg, hFFFFFFFF ' Invert on every edge 545 546 if_nz rcr hid_lp_reg+0, #1 nr,wc ' Repeat the last bit, or tap a new bit. 547 if_z test hid_fsk_reg, hid_fsk_mask wc 548 549 ' Now the detected FSK bit is in the carry flag. Feed it into 550 ' a 32-bit-wide low pass filter, which is close enough to our 551 ' bit length of 50 cycles. We correct the 32-bit filter's DC bias 552 ' using a 256-bit filter. This helps us maintain an even duty cycle 553 ' in the received manchester bits. 554 555 if_nc add hid_lp_s32, #1 556 if_nc add hid_lp_s256, #1 557 rcl hid_lp_reg+0, #1 wc 558 if_nc sub hid_lp_s32, #1 559 rcl hid_lp_reg+1, #1 wc 560 rcl hid_lp_reg+2, #1 wc 561 rcl hid_lp_reg+3, #1 wc 562 rcl hid_lp_reg+4, #1 wc 563 rcl hid_lp_reg+5, #1 wc 564 rcl hid_lp_reg+6, #1 wc 565 rcl hid_lp_reg+7, #1 wc 566 if_nc sub hid_lp_s256, #1 567 568 mov r0, hid_lp_s256 569 shr r0, #3 570 cmp hid_lp_s32, r0 wc 571 572 ' Now the carry flag holds a filtered version of the post-FSK-detection 573 ' signal. These are manchester-encoded bits. We need to detect the header, 574 ' validate the manchester encoding, and extract the actual data bits 575 ' from the received signal. This is a typical data/clock recovery problem. 576 ' We could use a PLL, but when doing this in software it's easier and less 577 ' error-prone to just brute-force it, and try to detect a signal at all 578 ' possible phases. 579 ' 580 ' At this point there are 100 possible phases, and we need enough room to 581 ' store 51 bits. (45 data bits + 3 bits each from 2 headers) We'll use a 582 ' 64-bit shift register for each phase. 583 ' 584 ' To cut down on the amount of memory required, we'll decimate the signal 585 ' by processing on only every other carrier cycle. This means we only have 586 ' 50 possible phases. 587 588 test pulse_count, #1 wz ' Decimate x2 (Opposite phase from the EM4102 decoder) 589 if_z jmp #rx_hid_ret 590 591 :shift1 rcl hid_bits+1, #1 wc ' Shift in the new filtered bit 592 :shift2 rcl hid_bits+0, #1 593 :load1 mov hid_shift+0, hid_bits+0 ' And save a copy in a static location 594 :load2 mov hid_shift+1, hid_bits+1 595 596 add :shift1, dest_2 ' Increment hid_bits pointers 597 add :shift2, dest_2 598 add :load1, #2 599 add :load2, #2 600 cmp :shift1, hid_shift1_end wz 601 if_z sub :shift1, dest_100 ' Wrap around 602 if_z sub :shift2, dest_100 603 if_z sub :load1, #100 604 if_z sub :load2, #100 605 606 ' Also save a 180-degree phase shifted version of hid_shift, 607 ' for doing manchester validation and header detection. 608 ' The bits in hid_shiftp are delayed by 1/2 bit relative to 609 ' hid_shift. 610 611 :load3 mov hid_shiftp+0, hid_bits+0+50 612 :load4 mov hid_shiftp+1, hid_bits+1+50 613 add :load3, #2 614 add :load4, #2 615 cmp :load3, hid_load3_end wz 616 if_z sub :load3, #100 617 if_z sub :load4, #100 618 619 or dira, #|<7 620 test hid_shift+1, #1 wz 621 muxz outa, #|<7 622 623 rdlong r0, format_ptr wz ' Make sure the output buffer is available 624 if_nz jmp #rx_hid_ret 625 626 ' 627 ' Ok. At this point, we can validate the contents of hid_shift and 628 ' hid_shiftp, and extract the packet data if possible. hid_shiftp 629 ' has our actual packet data, and em_shift has the complement of that 630 ' data. The header sequence of "000111" turns into "001" in hid_shift 631 ' and "011" in hid_shiftp. 632 ' 633 ' To provide further validation, we look for both the current packet's 634 ' header and the next packet's header. So, the data bits are actually 635 ' in bits 3 through 47. 636 ' 637 638 mov r0, hid_shiftp+0 ' Check 1st header at -180° 639 and r0, hid_s_mask 640 cmp r0, hid_sp_check wz 641 if_z mov r0, hid_shift+0 ' Check 1st header at 0° 642 if_z and r0, hid_s_mask 643 if_z cmp r0, hid_s_check wz 644 645 if_z mov r0, hid_shiftp+1 ' Check 2nd header at -180° 646 if_z and r0, #%111 647 if_z cmp r0, #%001 wz 648 if_z mov r0, hid_shift+1 ' Check 2nd header at 0° 649 if_z and r0, #%111 650 if_z cmp r0, #%011 wz 651 652 if_z mov r0, hid_shiftp+0 ' Check Manchester encoding 653 if_z xor r0, hid_shift+0 ' (high long) 654 if_z xor r0, hFFFFFFFF 655 if_z and r0, hid_data_mask wz 656 if_z mov r0, hid_shiftp+1 ' (low long) 657 if_z xor r0, hid_shift+1 658 if_z xor r0, hFFFFFFFF 659 if_z andn r0, #%111 wz 660 661 if_nz jmp #rx_hid_ret ' Exit on any decoding error 662 663 ' Hooray, we have a correct-looking packet. Justify and 664 ' trim the 45 bits of data, and store it. 665 666 667 mov r0, hid_shiftp+0 668 mov r1, hid_shiftp+1 669 670 and r0, hid_data_mask ' Cut off 1st header and unused bits 671 672 shr r0, #1 wc ' Cut off 2nd header 673 rcr r1, #1 wc 674 shr r0, #1 wc 675 rcr r1, #1 wc 676 shr r0, #1 wc 677 rcr r1, #1 wc 678 679 mov r2, hid_buf_ptr ' Write the result 680 wrlong r0, r2 681 add r2, #4 682 wrlong r1, r2 683 wrlong c_format_hid, format_ptr 684 685 rx_hid_ret ret 686 687 hid_shift1_end rcl hid_bits+1+100, #1 wc ' This is ":shift1" above, after we've 688 ' passed the end of the hid_bits array. 689 690 hid_load3_end mov hid_shiftP+0, hid_bits+0+100 691 692 693 '------------------------------------------------------------------------------ 694 ' Initialized Data 695 '------------------------------------------------------------------------------ 696 697 hFFFFFFFF long $FFFFFFFF 698 h80000000 long $80000000 699 h7FFF0000 long $7FFF0000 700 hFFFF long $FFFF 701 zero long 0 702 c_format_em long FORMAT_EM4102 703 c_format_hid long FORMAT_HID 704 705 dest_2 long 2 << 9 706 dest_100 long 100 << 9 707 dest_64 long 64 << 9 708 input_mask2 long |<INPUT_PIN 709 710 init_dira long |<CARRIER_POS_PIN | |<CARRIER_NEG_PIN | |<THRESHOLD_PIN | |<DEBUG_PIN | |<SHIELD1_PIN | |<SHIELD2_PIN 711 init_frqa long 0 712 init_frqb long DEFAULT_THRESHOLD 713 init_ctra long (%00101 << 26) | (CARRIER_POS_PIN << 9) | CARRIER_NEG_PIN 714 init_ctrb long (%00110 << 26) | THRESHOLD_PIN 715 716 carrier_mask long |<CARRIER_RATE - 1 717 carrier_bit0 long |<(CARRIER_RATE + 0) 718 carrier_bit1 long |<(CARRIER_RATE + 1) 719 720 pulse_target long 0 721 next_hyst long 0 722 hyst_constant long 0 723 724 baseband_reg long 0,0,0,0,0,0,0,0 725 baseband_s32 long 32 ' Number of zeroes in last 32 baseband bits 726 baseband_s256 long 256 ' Number of zeroes in last 256 baseband bits 727 728 em_buf_ptr long 0 729 hid_buf_ptr long 0 730 format_ptr long 0 731 pulse_ptr2 long 0 732 733 hid_header long |<16 - 1 734 hid_max_bits long 512 735 hid_fsk_mask long |<9 736 737 hid_lp_reg long 0,0,0,0,0,0,0,0 738 hid_lp_s32 long 32 ' Number of zeroes in last 32 FSK bits 739 hid_lp_s256 long 256 ' Number of zeroes in last 256 FSK bits 740 741 hid_s_mask long %111 << (3 + 45 - 32) 742 hid_s_check long %011 << (3 + 45 - 32) 743 hid_sp_check long %001 << (3 + 45 - 32) 744 hid_data_mask long (1 << (3 + 45 - 32)) - 1 745 746 747 '------------------------------------------------------------------------------ 748 ' Uninitialized Data 749 '------------------------------------------------------------------------------ 750 751 r0 res 1 752 r1 res 1 753 r2 res 1 754 755 cur_pulse res 1 756 prev_pulse res 1 757 pulse_len res 1 758 759 pulse_count res 1 760 prev_frqb res 1 761 762 hid_bit_len res 1 763 hid_bit_count res 1 764 hid_fsk_reg res 1 765 hid_reg res 1 766 767 em_bits res 64 ' 64-bit shift register for each of the 32 phases 768 em_shift res 2 ' Just the current shift register 769 em_decoded res 2 ' Decoded 40-bit EM4102 packet 770 em_parity res 1 ' Column parity accumulator 771 772 hid_bits res 100 ' 64-bit shift register for each of the 50 phases 773 hid_shift res 2 ' Just the current shift register 774 hid_shiftp res 2 ' 180 degrees out of phase with hid_shift 775 hid_decoded res 2 ' Decoded 45-bit HID packet 776 777 fit
125 kHz RFID Reader
http://www.changpuak.ch/electronics/RFID-READER-125kHz.php
A small circuit to read RFID badges operating at 125 kHz (EM4102)
This circuit is used here : Coffee Tally Sheet • Kaffee-Strichliste
Reading RFID-badges operating at 125 kHz is very easy. You just have to put a carrier (125 kHz) into the air and have the badge do the work.
The badge will do the ASK (Amplitude Shift Keying) and you only have to monitor the envelope of the carrier.
This can be done by a simple diode rectifier.
The carrier is generated by a 74HC4060 and a 4 MHz crystal.
Using "Q4" delivers the desired 125 kHz signal.
The AM Demodulator is build of a AA113 (for historic reasons) and R5, C5.
The demodulator is followed by a limiting amplifier.
The voltage across the coil is about 14 Vpp.
The envelope of the carrier, when a tag / badge is brought close to the coil ...
ProxClone - Proximity Card Reader / Cloner
http://proxclone.com/reader_cloner.html
Reader / Cloner Overview
The picture below is of my prototype combination card reader and cloner.
The unit is self contained and does not require the use of a PC or other external equipment to operate.
Operation is simple and straightforward.
Simply hold a card near the antenna and the unit reads and decodes the information from the card.
The information is then formatted and displayed on a 4x20 character LCD.
If the operator wishes to make a copy of the card he simply brings a T5557/5567 Read/Write card near the antenna
and presses the "write" button.
The LED flashes and in less than a second the R/W card has been programmed with the information that was read from the original.
Voila !! - A clone card.
The cost to build the device was minimal (approx. $30) including the LCD display and circuit board.
The design fit on a single sided circuit board that I etched myself.
The PWB was made to be the same size as the LCD so that they could be plugged together as a single assembly.
A detailed description of my design concept is included below.
Background
I initially began this activity by trying to build a simple card reader that could be used
to obtain all of the information that was transmitted by the card during a simple read operation.
Most commercial card readers do not output all of the data that is read.
Information such as the header and card format are never transmitted as part of the readers normal output stream.
Knowing this information is critical for being able to replicate a cards operation.
As a result, I set out to build my own custom reader.
The Design
A document that I found to be invaluable during my learning process was
Microchip's 125 Khz RFID Sysem Design Guide which can be found on their website.
Their FSK reference design circuit was basically what I used for my design.
I made a couple of small modifications to simplify the design and to allow the use of a Parallax SX28 microcontroller instead of the PIC.
A photo of my initial "reader-only" design (without write capability) is shown below:
After studying the datasheet for the T5557/5567 IC (used in many vendors access cards),
I soon realized that the reader circuit would only have to be modified slightly in order to also be able to function as an RFID writer.
To function as a writer the design simply needed to be able to modulate the 125Khz RF carrier using On/Off Keying (OOK) modulation
since this is how the T55x7 chips are programmed.
I modified the design to accomplish this by basically giving the microcontroller the ability to control the 125 Khz divide counter reset signal.
An extra push button was also added to an unused GPIO input on the microcontroller.
A schematic of my reader circuit (modified to become a writer) is shown below:
The reader/writer circuit design can be broken down into four main components.
1) The clocking circuit, which generates a 4 Mhz clock for the microcontroller and a 125Khz carrier signal for the RFID interface.
2) The RF front end consisting of a tuned LC resonator and an AM peak detector
3) A series of low pass and band pass filters to extract the 12.5Khz and 15.6Khz FSK signals.
4) The SX28 microcontroller which performs the following functions:
- LCD initialization
- Decoding and storage of the FSK data from the op amp filter output.
- Parsing and formatting of the card data.
- Driving the LCD display.
- Programming the clone card by modulating the 125 Khz carrier (per the T55x7 datasheet).
My updated "write-capable" version of the Reader/Writer assembly along with a commercial 44780 4x20 LCD
and 125Khz loop antenna is shown in the photo below.
The completed unit was installed onto a piece of acryllic plastic in lieu of trying to find an off-the-shelf plastic box
that everything would fit in and still look halfway decent.
I have tested the unit with numerous card types including 26-bit, 34-bit, 36-bit, 37-bit and the 35-bit Corporate 1000 formats.
The cloner was able to duplicate all of them without any difficulty.
In all cases, the vendors own commercially available readers were unable to distinguish between the original and the clone cards.
Final Design Project -
RFID Proximity Security System
https://instruct1.cit.cornell.edu/Courses/ee476/FinalProjects/s2006/cjr37/Website/index.htm
Introduction and Motivations:
For our final project, we designed and built (and exhaustively tested) an RFID-based proximity security system
for use with Cornell Identification cards, which have been RFID-embedded since fall of 2003.
The idea for this project was sort of spawned from our general interest in RFID technologies
and the near-simultaneous occurance of Lab 2 (Keypad Security System)
and the antiquated lock system at our fraternity house breaking.
At the highest level, our device uses an antenna coil to power the RFID tag embedded in our Cornell ID's
and read the induced response from the card.
This response is then filtered and manipulated into useful data and interpreted by the Atmel Mega32 microcontroller
which runs the actual security program. In addition to interactions with the ID cards, the system is in contact
with an administrator computer via a serial communications link and hyperterm.
The security system can store up to 20 45-bit codes which are derived from communications with each unique RFID tag.
If a card is read and it is not in the code database, a red LED flashes for 3 seconds.
Likewise, if the code can be found in the database, a green LED lights for 3 seconds.
From hyperterm, the administrator has the power to add codes, delete codes, list all codes,
"unlock" the door (the equivalent of the green LED flashing),
and initialize routines which allow codes to be added to the database by gathering data from the reader itself.
Educational topics explored in this lab include (but are not limited to)
passive filter design, active filter design, amplification circuits, RF antenna design, digital logic, serial communications, RFID theory,
pin interrupts, timer interrupts, and soldering.
In short, for this project we used elements of basically every introductory level ECE course we have taken.
Since we are dealing with such a complicated topic, on the hardware side of things we tried to rely as much as we could on proven circuit designs.
This would enable us to focus more on getting our system working well as a whole rather than spending countless hours debugging small parts of our project.
For this, the Microchip® microID 125 KHz Reference Guide (see citations section) proved to be an invaluable resource for both theory and results.
High Level Hardware Design:
Before we start with actual circuit design, it is neccessary to understand the principals behind the technology
that this project has set out to harness; passive RFID communications.
Passive RFID tags work in such a way that they are actually powered by an external signal,
which, in most cases is the carrier signal from the tag reader circuit.
These tags are fairly simple and are comprised of merely an L-C antenna (similar to the one shown in the block diagram below)
and the circuitry neccessary to modulate this carrier signal once powered on.
The reader and tag communicate using magnetic coupling since their respective antennas can sense changes in magnetic field,
which is observed as a change in voltage in the reader circuit.
The Cornell ID cards we use in this project were developed by HID®; specifically the HID DuoProx II cards.
These are useful because they have both embedded RFID as will as a magnetic strip, while much of campus is starting
to switch over to proximity entry systems, many current systems (including the dining halls) are still swipe-operated.
From looking at their website, it was difficult to determine much information about the card's operation,
asside from the fact that it operates at a 125 KHz carrier frequency and it could have a tag size anywhere between 26 and 128 bits long.
After many hours of research we discovered that the modulation type used in the cards is Frequency Shift Keying (FSK),
one of the more common ways used in RFID.
FSK modulates the signal by essencially multiplying a lower amplitude, lower frequency signal with the carrier signal, creating an AM-like effect;
the lower frequency enveloping the carrier frequency.
To switch between a "1" and a "0", the tag switches the modulating frequency.
The two frequencies used by our cards were 12.5 KHz (125 KHz/10) and 15.625 KHz (125 KHz/8), which correspond to 1 and 0 respectively.
The modulation produces an effect that looks similar to the figure below:
Figure 1: Simulation of FSK Modulation With Modulation Frequencies of 12.5 and 15.625 KHz and Carrier Frequency of 125 KHz
The job of the reader circuit is to provide the 125 KHz carrier frequency, transmit that to the tag, and detect the magnetic coupling from the tag,
which should look like the figure above.
In order to interpret this data, the carrier frequency must be removed, and the enveloping frequencies must be magnified into something measureable.
The block diagram/flow chart for our reader circuit can be found in the figure below:
Although each individual part of the circuit and program will be described in detail later,
the general idea for circuit operation is as such:
The Mega32 provides a timer-driven 125 KHz square wave for our carrier frequency.
This is then sent through an RF choke, which is essentially a passive low-pass filter with steep drop-off
to knock out the upper harmonics and leave us with only a sine wave.
The sine wave is then amplified using an emmitter follower PNP transistor and a half bridge to maximize current.
Since our resonant circuit is a series L-C circuit, maximum resonance is achieved at minimum impedance,
so it is very important that we provide adequate current amplification as to not overdrive our microcontroller.
To help reduce the strain (and ramp up the current more) further,
the square wave output from the MCU is put through parallel inverters.
On the recieving end, the signal is first half-wave rectified, since the negative part of the signal doesn't really make a difference,
and is then fed through a half-wave R-C filter to help knock out most of the 125 KHz carrier and detect the envelope signal.
This signal is then bandpass filtered using a series a Twin-T active bandpass filters,
and lowpass filtered with an active Butterworth filter to further decrease gain in frequencies outside of the 10-20 KHz area
and increase gain of the envelope signals such that it saturates the op-amps of the filters.
As a final stage the signal is put through a comparator and resistive divider to produce a nice square wave at logic levels.
Some D-flip flops and a decade counter are used to extract data from the modulating square waves.
Which are fed into the MCU and processed.
Hardware and Software Tradeoffs:
There are many ways to design a proximity card reader in terms of tradeoffs between hardware and software.
In most cases, software is cheaper because you don't need to purchase any parts but at the same time you are costing the MCU processing time.
Using more hardware will obviously increase the cost of the design but ultimately may alleviate painfully tedious optimizations
that would have been necessary had you used code to replace a component or device.
One of the first tradeoffs we encountered was whether to use the Mega32 or a separate counter to generate the 125 kHz carrier frequency.
The microID 125 kHz RFID System Design Guide suggested using a 14-stage binary counter to divide the clock from the crystal to 125 kHz.
However, since the Mega32 has built-in hardware timers that can output to one of the pins, there was no need to use a counter.
Another tradeoff we encountered was whether to use DSP or hardware to analyze the signal on the antenna.
Recall that this signal is the carrier signal and the magnetically coupled response from the card superimposed onto each other.
Using DSP, we could sample at the Nyquist frequency and compute the FFT of the signal
to find what frequencies are present in the response and from there decode the response.
If we were to use DSP, we would have to sample at greater than 250 kHz
meaning there would only be 64 cycles between samples to compute the FFT.
This imposed a huge constraint on the rest of our security system so we decided to implement the most of the decoding in hardware.
Specific Circuit Elements:
Transmit Stage: RF Choke and Power Amplifier:
The circuit of Figure 3 below is an RF choke followed by a current buffer and half-bridge amplifier.
The RF choke is used to filter out most, if not all of the upper harmonic frequencies found in the square wave output from the MCU,
leaving the fundamental frequency, 125 KHz, as a sine wave to be amplified.
The square wave generator seen in the figure below is, in actuality, the output from the MCU and a set of inverters to ramp up the current.
Diodes are used in the half bridge to help reduce crossover distortion caused from differing points of either transistor in the half bridge turning on and off.
In our design we used the 2N3904 and 2N3906 NPN and PNP BJT transistors from the lab since they were cheap and convenient.
In order to get better amplifier gain, and thus increase read range of our circuit, we could have used power MOSFETS instead for the half-bridge,
but we found the BJT's gave us a mostly acceptable level of gain, especially once the circuit was tuned.
Resonant Antenna Circuit:
While this portion of our circuit is only comprised of two components, it is also arguably the most important hardware element;
if it performs poorly then our security system performs poorly.
Because this design was recommended for proximity solutions from the Microchip® guide,
we decided to go with a series L-C resonant curcuit as opposed to one where the resistor and inductive antenna were in parallel.
Because of this, at maximum resonance we also observe maximum current.
In order to determine values for the inductance and capacitance needed, we used the equation:
where f0 is the resonant frequency (in Hertz), L is inductance (in Henries) and C is capacitance (in Farads).
Since f = 125 KHz and we had plenty of 1 nF ceramic capacitors in the lab, we settled on an inductance of 1.62 mH
To construct an antenna with the neccessary inductance we used coils of laquered copper wire, since it works well and is fairly compact.
In our final construction revision, we used a rectangular-shaped antenna coil since it fit well with the design.
The figure above shows the coil as circular, which is what we used for most of our preliminary testing
before we actually put the unit together.
Both antennas operated roughly the same as each other, although the rectangular coiled one resonates more.
Inductance for the rectangular coil is determined by the following equation:
where L is in microHenries, x and y are the width/length of the coil (in cm), h is the height of the coil (in cm),
b is the width across the conducting part of the coil (in cm) and N is the number of turns.
In our case, x=3.6cm, y=13.8 cm, h=1 cm, and we estimated b=.3 cm.
Using the equation, we calculated the coil to need approximately 90 turns.
It turned out this was a pretty good estimate.
After constructing the coil, we proceeded to tune it by removing coils until we saw the highest resonant voltage from our carrier frequency,
which was at roughly 88 turns.
Oscilloscope results from this circuit, with both just the carrier frequency, and with the modulated signal from the RFID tag can be seen below.
Half-Wave Rectify and Filtering:
This portion of the circuit is devoted to separating out the carrier frequency from the modulating envelope,
since its really only the envelope that has the data we care about.
The first stage is half-wave rectifying the signal to make things simpler and then filtering it slightly with an R-C filter.
As is the norm for filtering AC signals in this manner there is some 125 KHz ripple,
but choosing good values we could make the enveloping frequencies stand out from the ripple.
For this we chose R=390 KOhms and C= 2.2 nF.
Scope readings are shown in the figure below.
Note that the peaks are the ripple, and the whole signal seems to oscillate at 15.625 KHz.
You can tell this because there are 8 125 KHz ripple peaks per oscillation of the envelope.
At a 12.5 KHz envelope, there would be 10 ripples per oscillation.
Once signal leaves this stage, it passes through a capacitor to knock out the DC offset and into the next set of filters;
a pair of active Twin-T filters and an active Butterworth filter with the TL084 OpAmp as the gain element.
The circuit diagram for this is in the figure below:
Figure 8: Circuit Diagram for the Filter Stage. Signal Comes in From the Right and Exits out the Butterworth on the Left
As can be seen from the Bode Plot in the figure below, the first filter mostly isolates the pass band (10-20 KHz),
with roughly unity gain for all frequencies outside the pass band.
The second filter further accentuates gain in the pass-band while slightly reducing the magnitude of frequencies outside the pass band.
After this, the signal goes through a massive Butterworth Low-Pass filter to drastically increase gain of lower frequencies already in the pass band
and virtually eliminate the higher frequencies, including the 125 KHz carrier signal.
Once out of the filters, the signal is then put through a TL084-based comparator and a resistive divider to generate a nice square wave at logic levels.
The 12.5 KHz and 15.625 KHz frequencies come out of the filters beautifully.
When no card is present, the system reports a 28.75 KHz wave
which represents the highest frequency to come out of the filters with enough gain to saturate the opamps.
Figure 10: Protoboard With Our Filter Circuit (left) and 28.75 KHz idle Frequency From the Filter Output
Figure 11: Envelope Frequencies After Filtering and Reduction to Logic Levels. 12.5 KHz is on Left While 15.625 KHz is on Right
Data Creation Stage:
Technically, from the output of the comparator we should have been able to read and interpret data from the card using a timer interrupt.
We quickly realized, however, that by doing this we would cripple the functionality of our system.
In order to accurately measure the frequency of the incoming data stream we would realistically need to sample at 125 KHz,
which means that, with a clock rate of 16 MHz we would have 128 clock cycles to compute everything before the next sampling interrupt fired.
This would have been extremely difficult to implement.
Looking for an alternate method to obtain data, we found a brilliant design in the Microchip reference guide,
which makes use of flip-flops and a decade (Johnson) counter. This circuit can be seen in the figure below:
The way this works is as follows:
The comparator output serves as the clock for the first D flip-flop, which also takes logic 1 as its D value.
On the rising edge of the comparator clock, Q is immediately set to 1.
However, simulataneously ~Q goes low and clears the flip-flop.
This creates an extremely short pulse which serves as a reset for the decade counter and clock for the second flip-flop.
The decade counter is just a 1-hot counter which takes a 125 KHz clock.
With every rising edge of this clock, the counter outputs the next pin to logic 1;
so typical output would look like (if one were looking at output pins 0-9 of the counter) 1000000000 0100000000 00100000000 etc.
However, this counter is being reset with every rising edge of the comparator output.
Thus, since we've already determined that 125 KHz/10 = 12.5 KHz is to be our frequency that represents logical 1,
all we have to do is check for the output on pin9 to know whether or not we see that frequency.
If the system is operating at either one of the other possible frequencies, the counter will be reset before pin9 can go active.
The pin9 output serves as input to the second flip-flop and also to the clock inhibitor, which keeps the 9th pin high until the counter is reset.
Because of this set-up, the Q output of the second flip-flop will remain logical 1 so long as modulating frequency is 12.5 KHz
and will drop down to 0 if its anything else.
Theoretically, this circuit should work perfectly.
However, experimentally it did not, and thus required a small modification.
The 100 KOhm resistor on the first flip-flop serves to lengthen the time it takes for the ~Q signal to get to CLEAR.
Since all transistors have some amount of natural capacitance, this forms an RC circuit of sorts with a set RC time constant for the signal to rise or fall.
As it turns out, this time was too short for the decade counter.
The original design from the reference guide specified only a 10KOhm resistor between ~Q and CLEAR.
With the 10 KOhm resitor, pulse widths for the reset pulse were a mere 50 ns long, while the counter required at least 250 ns.
This caused some very eratic behaviour.
After many hours of debugging, we finally pinpointed the problem and replaced the resistor with the 100 KOhm resistor
which increased the pulse width long enough for the counter to correctly operate.
The figures below show the behaviour of the circuit when operating correctly:
Figure 13: Comparator Output with Reset Pulse (left). Comparator Output with Data Output (right)
Figure 14: A Close-Up of our Reset Pulse Reveals That it is now 350 ns; 100 ns Over the Minimum for the Decade Counter
Software Design and Program Details:
Initialize:
This function initializes the various interrupts, timers, input-output,
and global variables utilized in the program. The following are all initialized or setup here:
- Timer2 is used to toggle OC2 to generate a 125 kHz square wave.
- Port A is the output of the digital counter/flip-flop circuit.
- Port C is the LED output.
- Timer0 is used to count milliseconds for keeping track of time
and controlling the timing on scheduled tasks and timer0 interrupt is enabled.- Enable the transmitter and receiver in the USART and set the baud rate to 9600.
- Initialize flags, counts, buffers, and other state variables.
- External interrupt 2 is enabled and set to trigger on the rising edge.
- Set interrupt bit.
- Hold and prompt for the date and time before entering the while(1) loop.
USART Receive Interrupt
This interrupt handles any typed characters received from the terminal through the RS232 connection
and stores it into a buffer.It also echoes the character to the terminal. Once a carriage return is detected, the receive-ready flag is set indicating
that a line command has been entered by the user via the terminal.
USART Transmit Interrupt
This interrupt handles transmitting characters to the terminal. It simply loops through the transmit buffer until the last character is sent and then is ready for another transmission.
Timer0 Compare Match Interrupt
This interrupt serves as a timer to control the scheduling of different timers.
It decrements the timers for timing how long the door is unlocked, timing how long a second lasts, and for checking the receive-ready flag.
External Interrupt2
This interrupt samples the data (output of the counter/flip-flop circuit). It is triggered by the rising edge of the output of the comparator. The output of the comparator is approximately a 29 kHz square wave if there is no modulation, and a 15.625 kHz or 12.5 kHz square wave if there is modulation. When the output of the comparator is 15.625 kHz the data is 0 and when the output is 12.5 kHz the data is 1. The point of this interrupt is to detect how many bits are in a 0 or 1 pulse of the data. Thus every time this interrupt fires, we increment a counter and add the sample to an array.
Main Method
In main, there are 3 major modes of operation.
The mode of operation at startup is normal;
however the modes can be changed through the terminal.
In normal mode, the reader waits for External Interrupt2 to finish reading a response from the card.
It does this in about a thousand executions of the interrupt.
When enough bits have been sampled, we turn off the External Interrupt2.
When first analyzing the response from the card, we noticed periodicity every 540 bits.
Thus, to guarantee that our sample window captures a full continuous 540 bit cycle,
we sampled 1080 bits before turning off the interrupt.
An example of a 1080 bit response looked something like this:
001111100000011111000000111111000000000000111111111100000011111000000000000111111000000111110000001111100000011111100000 11111100000011111111110000000000001111110000011111100000011111111110000000000001111110000011111111111000000111110000001111
00000000000011111100000011111111110000000000001111110000011111100000000000000000011111111111111110000011111100000011111000000 1111100000011111100000111111000000111110000001111111111000000000000111111111110000001111100000000000011111100000111111000000
111110000001111111111000000111111000001111110000001111100000011111000000111111000000000000111111111100000011111000000000000
1111110000001111100000011111000000111111000001111110000001111111111000000000000111111000001111110000001111111111000000000000
11111100000111111111110000001111100000011111000000000000111111000000111111111100000000000011111100000111111000000000000000000 11111111111111110000011111100000011111000000111110000001111110000011111100000011111000000111111111100000000000011111111111000000
1111100000000000011111100000111111000000111110000001111111111000000111111000001111110000There is a long sequence of 1's and 0's that stand out;
we used these as references to identify the start and end of the 540 bit response.
To extract the 540 bit response we wrote a function to detect a sequence of 15 to 18 1's.
This function loops until it finds the start sequence and stores it in a global variable and calculates the end sequence.
There is also something noticeable about the 540 bit sequence.
There are 1's and 0's in groups of 5, 6, 10, 11, or 12 excluding the start and stop sequence.
Since 10, 11, and 12 can be made from combinations of 5 and 6,
we hypothesized that maybe these longer groups are combinations of two groups of 5 or 6.
With this in mind, we wondered whether a group of 5 or 6 bits possibly represents a single bit.
This would make sense because the card cannot perfectly transition from one modulated frequency to another without some transition.
Thus a group of 10, 11, or 12 represents two bits.
Thus the reduced (90 bit) version of the 540 bit response excluding the start and stop sequence is extracted
by detecting sequences of bits and replacing them with a single or a double bit:
010101010101011001101001010101101010101010011010010101010101100101011001011010100101100101
From this code, it is fairly obvious that the reduced sequence is encoded in Manchester code.
If you split up the 90 bit response into pairs of bits, there are transitions within each pair:
01 01 01 01 01 01 01 10 01 10 10 01 01 01 01 10 10 10 10 10 10 01 10 10 01 01 01 01 01 01 10 01 01 01 10 01 01 10 10 10 01 01 10 01 01
A transition from low to high corresponds to a 1 and a transition from high to low corresponds to a 0.
Since two bits correspond to a single bit, the Manchester decoded response has half the bandwidth and is thus only 45 bits long:
111111101001111000000100111111011101100011011
We believe this code is the raw data stored on the card.
We have not been able to decode this further to find it's relation to Cornell student ID number
but it is not necessary since this number is unique to each card.
After decoding the initial 540 bit response to this 45 bit code, we store this data and sampled again.
To prevent false reads, we keep sampling until we successfully read 3 consecutive identical codes.
If we get 3 consecutive identical codes, we compare this code to the code bank (where all the authorized codes are stored).
The code bank is stored in EEPROM due to limited space in SRAM.
If the 3 consecutive identical codes match a code in the code bank, a green LED is lit for 3 seconds to signify that the door is unlocked.
If the code does not match the code bank, a red LED is lit to signify that the door is not opened and that the code is not authorized.
A statement is also printed to the administrative terminal providing the card code,
whether the card was accepted, and the time at which the event occurred.
The other mode is called remote operation.
In this mode, the admin has a choice of remotely adding a code to a specific code bank position
or remotely adding any number of codes (bound between 1 and 20 inclusive).
When adding a code to a specific code bank position, we turn on External Interrupt2 and read the card response.
Just like in normal mode, we find the start code, reduce the sequence, and Manchester decode the sequence.
We do this until we read 5 consecutive identical codes and then store it into the specified position in the code bank.
When adding a specific quantity of codes, we first search through the status of the code bank and find the first unused position.
Then we go into remote add by position mode and we add the code at the first unused position.
We do this until either the specified quantity of codes are stored or until the code bank is full.
After either of these modes finishes executing, the reader goes back to its normal mode,
but now with the new stored codes in the code bank.
The end of the main loop serves as a scheduler that checks the timers for certain tasks and executes the task.
It executes the function to check the receive-ready flag, turns off the LED's after 3 seconds,
and executes the counter that keeps track of time and date.
Check Receive Ready
The last major part of the security system is the administrative interface through the terminal. This function checks to se if a command has been received from the admin. It checks the command and executes the corresponding actions. The commands that are implemented are:
- a <pos> <code> – add a code
- l – list all codes
- d <pos> – delete a code
- u – unlock door
- rp <pos> – remote add (positional)
- ra <amt> – remote add (amount)
Helper Functions
The rest of the functions are simple, self-explanatory, helper functions that accomplish simple tasks such as keeping track of the time and date, storing codes into the code bank, verifying that a code matches a code in the code bank, comparing two codes, copying a code, and completing or starting a receive or transmit using the USART.
Results of Design:
Certain aspects of the proximity security system performed equal to what we initially expected at the start of the project.
The maximum range of the proximity card reader is about 1.5 to 2.0 inches from the antenna coil.
According to the microID 125 kHz RFID System Design Guide, different dimensions of reader and tag coils will affect the maximum read distance.
For example, a 3 x 6 inch reader antenna and a 0.5 inch diameter card antenna will have a maximum read distance of about 1.5 inches.
For a 1.0 inch diameter card antenna the read distance increases to 4 inches.
Since we do not know the exact dimensions of the antenna inside of our Cornell University identification cards,
we do not know whether we are performing below or above ideal expectations,
however, the current read range is sufficient for our purposes.
The approximate read time for most cards is about 1 to 3 seconds once the card is within the maximum read distance.
The latency is due to the redundant code check.
Once the card is in range and we start receiving data, we read the data 3 consecutive times.
If the code ever differs, then we try to read another 3 consecutive times.
Thus, if the card is near the maximum range or has poor modulation consistency then it will take longer to read.
The proximity security system is fairly accurate as we expected.
There are little to no false positives, although sometimes there are false negatives.
If the card is held around the maximum range, we occasionally receive incorrect data.
However, if the card is within an inch of the coil, the data can be read with virtually no errors.
Although important, safety was a minor concern when designing a proximity security system.
Since there is no contact between the user and the reader, there is no danger of harming the individual through direct contact.
The 125 kHz signal transmitted from the reader is also harmless but may cause interference with other devices operating at the same frequency.
From our time in lab however, we noticed almost no interference from other people's designs.
Our goal for this project was to build a security system that used our current Cornell University identification cards.
Thus by completing that goal, we believe we have built a fully operational concept/prototype of an ideal proximity security system for our fraternity house.
This project is very usable by anyone wanting a hands-free front door security system while utilizing a card that is necessary to have with you anyway.
1 /* Authors: Ricardo Goto and Craig Ross 2 Project: RFID Security system 3 */ 4 5 #include <Mega32.h> 6 #include <stdio.h> 7 #include <delay.h> 8 9 /* Define Constants */ 10 #define NUM_BITS 1080 11 #define NUM_CODES 20 12 #define RED 0b11111110 13 #define GREEN 0b11111101 14 #define OFF 0b11111111 15 #define CHECK_RECEIVE_TIME 50 16 #define NORMAL 0 17 #define REMOTEPOS 1 18 #define REMOTEAMT 2 19 20 /* Define input pin from RFID circuit */ 21 #define RFIDIN PINA.0 22 #define LED PORTC 23 24 25 /* Prototypes */ 26 void initialize(void); 27 void gets_str(void); 28 void puts_str(void); 29 void find_start_code(void); 30 void reduce_sequence(void); 31 void manchester_decode(void); 32 unsigned char match_code(unsigned char *char1,unsigned char *char2); 33 void copy_code(unsigned char *char1,unsigned char *char2); 34 unsigned char verify_code(unsigned char char1[]); 35 void check_receive(void); 36 void time_counter(void); 37 void store_into_bank(char position, char code[], char offset); 38 39 /* Global Variables */ 40 char curr_sample; 41 char code_count; 42 char start_flag; 43 int totalBits; 44 char bit_array[NUM_BITS+1]; 45 int sample_buffer; 46 int start_sequence; 47 int end_sequence; 48 char reduced_array[91]; 49 char final_code[46]; 50 char code_check[46]; 51 char check_receive_timer; 52 int door_timer; 53 char mode; 54 signed char add_pos; 55 char del_pos; 56 char add_amt; 57 unsigned int seconds; 58 unsigned int minutes; 59 unsigned int hours; 60 unsigned int days; 61 unsigned char r_days[4]; 62 unsigned char r_hours[3]; 63 unsigned char r_minutes[3]; 64 char no_code[46]; 65 eeprom char code_bank[NUM_CODES][46]; 66 char temp[46]; 67 eeprom char bank_status[NUM_CODES]; 68 char r_index, r_buffer[128], r_ready, r_char; 69 char t_index, t_buffer[128], t_ready, t_char; 70 71 /* USART recieve interrupt */ 72 interrupt[USART_RXC] void uartr(void) 73 { 74 r_char = UDR; 75 UDR = r_char; 76 if (r_char == 8) 77 { 78 if(r_index != 0) r_index--; 79 } 80 else if (r_char != ' ') r_buffer[r_index++] = r_char; 81 else 82 { 83 r_buffer[r_index] = 0x00; 84 r_ready = 1; 85 UCSRB.7 = 0; 86 putchar(' '); 87 } 88 } 89 90 /* USART transmit interrupt */ 91 interrupt[USART_DRE] void uartt(void) 92 { 93 t_char = t_buffer[++t_index]; 94 if (t_char == 0) 95 { 96 UCSRB.5 = 0; 97 t_ready = 1; 98 } 99 else UDR = t_char; 100 } 101 102 /* Timer0 compare match ISR */ 103 interrupt [TIM0_COMP] void timer0_compare(void) 104 { 105 /* Decrement timers every millisecond */ 106 if(check_receive_timer > 0) --check_receive_timer; 107 if(door_timer > 0) door_timer--; 108 if (seconds > 0) --seconds; 109 } 110 111 /* External pin interrupt */ 112 interrupt[EXT_INT2] void int2(void) 113 { 114 /* In this interrupt, we want to sample the output of the 115 second DFF just after the rising edge of the output of 116 the comparator stage. This will tell us how the length 117 of the pulse corresponds to the number of bits. We only 118 start caring about the data when it first goes high, 119 however, the card will probably be close to the 120 maximum reading distance. Therefore, we wait until the 121 card gets closer (we assume the person is bringing the 122 card to about 1-2 inches from the reader coil) and then 123 we start to sample. Once we have enough samples to 124 capture a full period of the looping response, we stop 125 sampling. 126 */ 127 delay_us(16); 128 curr_sample= RFIDIN; 129 if((start_flag == 0) && (curr_sample == 1)) 130 { 131 start_flag= 1; 132 sample_buffer++; 133 } 134 else if((start_flag == 1) && (sample_buffer <= 500)) sample_buffer++; 135 else if((start_flag == 1) && (totalBits < NUM_BITS)) 136 { 137 if(curr_sample == 1) bit_array[totalBits++]= 49; 138 else bit_array[totalBits++]= 48; 139 } 140 } 141 142 143 void main(void) 144 { 145 char i; 146 initialize(); 147 while(1) 148 { 149 if((totalBits == NUM_BITS) && (door_timer == 0) && (mode == NORMAL)) 150 { 151 /* Normal Operation: 152 When a full period of the looping card response is 153 captured, we want to decode the response. We execute 154 the following steps until we get 3 identical codes 155 in a row. 156 - turn off the external pin interrupt since we are 157 not going to be reading anything in this window of 158 time 159 - look for a start code and take only data 160 - reduce the bit sequence to 90 bits 161 - manchester decode to 45 bits 162 If we get 3 identical codes, then we compare that code 163 with the code bank to see if the code is currently 164 allowed access to the facility. If so, we blink a green 165 LED (open the door) for 3 seconds. If not, then we 166 blink a red LED (do not open the door) which also lasts 167 for 3 seconds. 168 */ 169 GICR= 0b00000000; 170 find_start_code(); 171 reduce_sequence(); 172 manchester_decode(); 173 if(code_count == 0) 174 { 175 copy_code(code_check,final_code); 176 code_count= 1; 177 } 178 else if(code_count == 1) 179 { 180 if(match_code(code_check,final_code)) code_count= 2; 181 else code_count= 0; 182 } 183 else if(code_count == 2) 184 { 185 if(match_code(code_check,final_code)) 186 { 187 if(verify_code(final_code)) 188 { 189 sprintf(t_buffer," Card ID: %s Access Granted at %03d:%02d:%02d ",final_code,days,hours,minutes); 190 puts_str(); 191 while(t_ready == 0){}; 192 LED= GREEN; 193 door_timer= 3000; 194 } 195 else 196 { 197 sprintf(t_buffer," Card ID: %s Access Denied at %03d:%02d:%02d ",final_code,days,hours,minutes); 198 puts_str(); 199 while(t_ready == 0){}; 200 LED= RED; 201 door_timer= 3000; 202 } 203 } 204 code_count= 0; 205 } 206 /* Make sure to reset the interrupt variables 207 and turn on the external interrupt. 208 */ 209 totalBits= 0; 210 start_flag= 0; 211 sample_buffer= 0; 212 GICR= 0b00100000; 213 } 214 else if(mode == REMOTEAMT) 215 { 216 /* Remote Quantity Add Operation: 217 If we are in this mode of operation, this segment of code 218 makes it so that we loop in Remote Positional Add until 219 the amount of codes have been added. Our algorithm searches 220 the code bank status to find open positions and uses Remote 221 Positional Add to add the card ID. 222 */ 223 add_pos= -1; 224 for(i= 0; i<NUM_CODES; i++) 225 { 226 if(bank_status[i]== 0) 227 { 228 add_pos= i; 229 bank_status[i]= 1; 230 break; 231 } 232 } 233 if(add_pos != -1) 234 { 235 mode= REMOTEPOS; 236 printf("Please place card in front of reader "); 237 delay_ms(500); 238 LED= GREEN & RED; 239 delay_ms(200); 240 LED= OFF; 241 delay_ms(300); 242 LED= GREEN & RED; 243 delay_ms(200); 244 LED= OFF; 245 add_amt--; 246 } 247 else 248 { 249 printf("Code bank is full, please delete unwanted codes and re-run "); 250 add_amt= 0; 251 mode= NORMAL; 252 } 253 } 254 else if((totalBits == NUM_BITS) && (mode == REMOTEPOS)) 255 { 256 /* Remote Positional Add Operation: 257 Similar to normal mode except we are storing the 258 next card into the code bank. To prevent a bad code 259 from getting into the code bank, we add even more 260 error protection by having to read the same code 5 261 times in a row before it is accepted into the code 262 bank. 263 */ 264 GICR= 0b00000000; 265 find_start_code(); 266 reduce_sequence(); 267 manchester_decode(); 268 if(code_count < 5) 269 { 270 if(code_count == 0) 271 { 272 copy_code(code_check,final_code); 273 code_count++; 274 } 275 else 276 { 277 if(match_code(code_check,final_code)) code_count++; 278 else code_count= 0; 279 } 280 } 281 if(code_count == 5) 282 { 283 store_into_bank(add_pos,final_code,0); 284 sprintf(t_buffer,"Code added to position %d ",add_pos); 285 puts_str(); 286 LED= GREEN; 287 delay_ms(200); 288 LED= RED; 289 delay_ms(200); 290 LED= GREEN & RED; 291 delay_ms(500); 292 LED= OFF; 293 code_count= 0; 294 if(add_amt > 0)mode= REMOTEAMT; 295 else mode= NORMAL; 296 } 297 totalBits= 0; 298 start_flag= 0; 299 sample_buffer= 0; 300 GICR= 0b00100000; 301 } 302 303 /* Check timers for timed tasks */ 304 if(check_receive_timer == 0) check_receive(); 305 if((door_timer == 0) && ((LED == GREEN) || (LED == RED))) LED= OFF; 306 if(seconds == 0) time_counter(); 307 } 308 } 309 310 /* Function: Check Receive 311 This function checks the r_ready flag every 50 milliseconds. If 312 there is a requested command in the r_buffer then we process the 313 command and execute the corresponding code. 314 */ 315 void check_receive(void) 316 { 317 char i,j; 318 check_receive_timer= CHECK_RECEIVE_TIME; 319 if(r_ready == 1) 320 { 321 /* Add input code at specific position */ 322 if(r_buffer[0] == 'a') 323 { 324 if(r_buffer[3] == ' ') 325 { 326 add_pos= r_buffer[2]-48; 327 if((add_pos >= 0) && (add_pos <= 9)) 328 { 329 store_into_bank(add_pos,r_buffer,4); 330 bank_status[add_pos]= 1; 331 printf("Code added at position %d ",add_pos); 332 } 333 else printf("Invalid code position "); 334 } 335 else if(r_buffer[4] == ' ') 336 { 337 add_pos= (r_buffer[2]-48)*10+(r_buffer[3]-48); 338 if((add_pos >=0) && (add_pos < NUM_CODES)) 339 { 340 store_into_bank(add_pos,r_buffer,4); 341 bank_status[add_pos]= 1; 342 printf("Code added at position %d ",add_pos); 343 } 344 else printf("Invalid code position "); 345 } 346 else printf("Incorrect syntax "); 347 } 348 /* List all codes and corresponding status */ 349 else if(r_buffer[0] == 'l') 350 { 351 putsf("Code Bank: "); 352 for(i= 0; i < NUM_CODES; i++) 353 { 354 for(j= 0; j < 45; j++) 355 { 356 temp[j]= code_bank[i][j]; 357 } 358 printf("%02d: %d - %s ",i,bank_status[i],temp); 359 } 360 } 361 /* Delete code at specific position */ 362 else if(r_buffer[0] == 'd') 363 { 364 if(r_buffer[3] == '