Firstly, it uses some weird protocol called Wiegand26 rather than the slightly more straight-forward serial/TTL level communications. But the protocol itself seems quite straightforward: the device consists of two data lines - when a zero is transmitted, one line goes low for a few microseconds, while the other stays high and when a one is transmitted, the other line goes low, keeping the first line high.
This seems easy enough. We simply monitor both lines and when one goes low, wait for it to come high again, and add a zero or a one to a binary buffer value. The tricky part comes in detecting the end of the binary stream:
Now the "official" Wiegand26 protocol involves 26 bits of data (that must be where it gets the -26 part of it's name from!). But some RFID cards and tags use much longer data for better security. Since we don't know much about the tags/cards we're going to use with this reader, we're going to have to assume there could be more or fewer bits of data coming down the wire(s).
So here's the approach:
We're going to set a flag to say whether or not we've started receiving some data.
As soon as one of the data lines changes, we start a timer running to time out after about 500ms.
Every time one of the data lines changes, we reset the timer.
When the timer times out (no activity for 500ms) we see what data we've got in the buffer and spit it out over serial.
We've doing this project in SourceBoost - not because we've fallen out with Oshonsoft, but because the first chip we picked up was one of the 16F1825s we've been using in our earlier audio project, so we stuck with it for ease of use.
This is the bulk of our program:
void main(){
init();
timer1Init();
delay_ms(50);
startTimer();
while(1){
checkforinput();
}
}
init();
timer1Init();
delay_ms(50);
startTimer();
while(1){
checkforinput();
}
}
The init routine simply sets up the input/output pins, and for debugging, starts the UART for writing data out over serial so we can debug what's actually going on inside the magic black box:
void init(){
osccon = 0b11110000; //32 MHz clock
intcon = 0b11000000; //global/peripheral ints enabled
apfcon0 = 0b01000000; //MOSI on RA4
option_reg = 0b10000000; //no weak pull-ups, timer0 at OSC/4/2 = osc/ 8
UARTInit();
ansela = 0; //no analog inputs
anselc = 0; //no analog inputs
trisa=00000000b;
trisc=00000110b;
porta=0;
portc=0;
tmp=0;
bitCount=0;
bufferPointer=0;
// now we pull pins low to activate the beeper and the lights
// so force them high by default
porta.2=1;
portc.0=1;
msCount=0;
isReading=0;
empty_buffer();
}
We couldn't be bothered to work out the values/pre-scalers for a 500ms timer, so just copied and pasted some code from another project which kept a 1ms timer, and used that with a counter to decide when 500ms had passed:
void preloadTimer1(){
//------------------------------------
// pre-load timer1 with 65172 (0xFE94)
//------------------------------------
// 1/22050 = 0.00004535147
// at 32mhz, fosc=8,000,000
// and 8m/22050 = 362.812
// so we want our timer1 to only count up to 363
// timer1 is a 16-bit timer (0-65535) so we need to
// preload it with 65535-363 = 65172
tmr1h=0xfe;
tmr1l=0x94;
}
void timer1Init(){
//-------------------------------------------
// interrupt on timer1 22050 times per second
// ------------------------------------------
pie1.0=1; // timer1 rollover interrupt on bit zero
intcon.7=1; // global interrupts
intcon.6=1; // peripheral interrupts
preloadTimer1();
}
void startTimer(){
t1con.0=1;
}
void stopTimer(){
// turn off the timer1 T1ON bit
t1con.0=0;
}
void resumeTimer(){
// turn on the timer1 T1ON bit
t1con.0=1;
}
And in our timer interrupt:
void interrupt(void){
// bit zero of pir1 is tmr1 overflow interrupt
if(pir1.0==1){
// clear the timer1 interrupt flag
pir1.0=0;
// preload timer1 to get the next interrupt to occur in a timely manner
preloadTimer1();
msCount++;
if(msCount>500){
if(isReading==1){
flushBuffer();
}
msCount=0;
}
}
}
// bit zero of pir1 is tmr1 overflow interrupt
if(pir1.0==1){
// clear the timer1 interrupt flag
pir1.0=0;
// preload timer1 to get the next interrupt to occur in a timely manner
preloadTimer1();
msCount++;
if(msCount>500){
if(isReading==1){
flushBuffer();
}
msCount=0;
}
}
}
Now every time one of the data lines goes low, we wait for it to come back high, then add a one or a zero to the end of a stream of bits.
void checkforinput(){
if(portc.2==0){
// reset the timer if this is the start of a read
if(isReading==0){msCount=0;}
// set the flag to say we're reading some data
isReading=1;
//data1 has gone low: this is a one
while(portc.2==0){
// wait for the line to return high
tmpBit=1;
}
add_bit();
}
if(portc.1==0){
// reset the timer if this is the start of a read
if(isReading==0){msCount=0;}
// set the flag to say we're reading some data
isReading=1;
//data1 has gone low: this is a one
while(portc.1==0){
// wait for the line to return high
tmpBit=0;
}
add_bit();
}
}
if(portc.2==0){
// reset the timer if this is the start of a read
if(isReading==0){msCount=0;}
// set the flag to say we're reading some data
isReading=1;
//data1 has gone low: this is a one
while(portc.2==0){
// wait for the line to return high
tmpBit=1;
}
add_bit();
}
if(portc.1==0){
// reset the timer if this is the start of a read
if(isReading==0){msCount=0;}
// set the flag to say we're reading some data
isReading=1;
//data1 has gone low: this is a one
while(portc.1==0){
// wait for the line to return high
tmpBit=0;
}
add_bit();
}
}
To add a bit to the right-most end of the temporary value, we bit-shift all the data in the variable one place to the left, and simply OR the value (1 or zero) to the final result.
Here's an example:
Let's say we've already got the value 001101 in our buffer and we receive a one. First we bit-shift the value one place to the left. This gives us 011010. Now we OR this value with the number one (000001) and the result is 011011. This is the same as just tacking a one onto the end of the bit stream!
void add_bit(){
tmp=tmp << 1;
tmp=tmp | tmpBit;
bitCount++;
if(bitCount>=8){
// this is a full byte
// add it to the buffer
porta.4=1;
buffer[bufferPointer]=tmp;
bufferPointer++;
tmp=0;
bitCount=0;
}
}
tmp=tmp << 1;
tmp=tmp | tmpBit;
bitCount++;
if(bitCount>=8){
// this is a full byte
// add it to the buffer
porta.4=1;
buffer[bufferPointer]=tmp;
bufferPointer++;
tmp=0;
bitCount=0;
}
}
So all that's left to do now is spit the data out so we can interrogate it:
void empty_buffer(){
unsigned char i;
for(i=0; i<=12; i++){
buffer[i]=0;
}
bufferPointer=0;
}
void flushBuffer(){
unsigned char i;
// send out whatever's in the buffer
// (first four bytes for a 26-bit protocol)
isReading=0;
UARTPrintLn("Buffer contents:");
for(i=0; i<4; i++){
tmp=buffer[i];
UARTPrintNumber(tmp);
UARTPrintLn(" ");
}
// reset the buffer for the next read
tmp=0;
bitCount=0;
empty_buffer();
}
unsigned char i;
for(i=0; i<=12; i++){
buffer[i]=0;
}
bufferPointer=0;
}
void flushBuffer(){
unsigned char i;
// send out whatever's in the buffer
// (first four bytes for a 26-bit protocol)
isReading=0;
UARTPrintLn("Buffer contents:");
for(i=0; i<4; i++){
tmp=buffer[i];
UARTPrintNumber(tmp);
UARTPrintLn(" ");
}
// reset the buffer for the next read
tmp=0;
bitCount=0;
empty_buffer();
}
The reader we're working with has the following pinout:
power
ground
data0
data1
buzzer (low to activate)
led (low = green, high = blue)
So we wired the whole lot up to our PIC 16F1825 microcontroller, dumped some code onto it and read back what came out:
Amazingly, everything worked pretty much the first time out, with the results as follows:
Amazingly, the RFID reader could read a variety of RFID tags. We tried it with some of the phonics owl cards from an earlier BuildBrighton project, as well as the key fobs from the BuildBrighton door entry system, along with some other RFID tags from other people's various places of work. The RFID reader simply read them all and reported back the tag contents!
Now it'd be easy to call it a day and say we can read RFID tags successfully, but there's still the nagging doubt that we're not actually reading data, and just reporting a load of junk. What we needed was some way of confirming the data being read with a known value.
Luckily, the phonics owl cards (and a lot of RFID cards for that matter) have a Wiegand26 value printed onto them. If you have an RFID card with two sets of values, it's the second value (the one with the comma in it) that we're interested in:
What we need to do is compare our read-in value(s) against the Wiegand26 values printed onto the cards. So let's take a look at the actual bitstream from each of the card reads:
card no: 0006304068 096,12612
0000000048
0000000024
0000000162
0000000000
b00110000000110001010001000 (first 26 bits)
card no: 0012225140 186,35444
0000000093
0000000069
0000000058
0000000000
b01011101010001010011101000 (first 26 bits)
card no: 0006348080 096,56624
0000000176
0000000110
0000000152
0000000000
b10110000011011101001100000 (first 26 bits)
0000000048
0000000024
0000000162
0000000000
b00110000000110001010001000 (first 26 bits)
card no: 0012225140 186,35444
0000000093
0000000069
0000000058
0000000000
b01011101010001010011101000 (first 26 bits)
card no: 0006348080 096,56624
0000000176
0000000110
0000000152
0000000000
b10110000011011101001100000 (first 26 bits)
We've reported four bytes of data, because 26 bits is just over 3 bytes (3x8 = 24 bits).
In this case, all our fourth bytes are zero, but there's no guarantee that this will always be the case. So let's convert each decimal value to binary and squash them all together (truncating once we get to 26 bits). Looking at it now, we could have created a single 32-bit value and simply bit-shifted the data 26 times, but then our code wouldn't work with 64-bit (or longer) cards. So by handling the data one byte at a time, we've got more work to do at this end (for validating) but it does mean we've a much more flexible system for future use.
Here's how the Wiegand26 protocol works, with 26 bits of data:
Let's look at the first set of values.
Splitting the byte stream up into groups of bits, we've got
0 01100000 0011000101000100 0
At the minute we're ignoring the parity bits, and are only interested in the "middle" section of the data stream. But already it's looking quite encouraging....
01100000 in decimal is 96
We've a couple of cards that begin with 096 followed by a comma.....
Taking the next group of 16-bits and converting to a single decimal gives us - 12612
That means we've correctly read the RFID data from the top-most card! Yay!
Comparing the values from the other cards, and we find we're accurately reading all the cards using the Wiegand26 protocol. We're not just reading junk, we're actually reading data.
With this in mind, we don't actually have to decode the bit-stream back to Wiegand values every time - we can simply report back the groups of bytes from the different cards, fobs and tags, confident that if we're reading them correctly (we are), the data from each will decode to the actual value on the card (if we need it to) but the parity bits will always give us the same groups of bytes for each card. As long as we have consistency each time a card is presented (i.e. we're not reading junk, we know we're reading the data correctly) we can simply compare the clumps of bytes to a look-up table to make the cards usable.
So to use this RFID reader for a door entry system, for example, it doesn't matter if we record a card as having the Wiegand26 value of 096,12612 or whether it's stored as 48,24,162,0 - each time the card with 096,12612 is presented to the card reader, we should expect to get back the bytes 48,24,162,0.
It's much easier to work with byte rather than splitting them up into bits, so we could just put these four bytes into our look-up table instead of converting back and forth between crazy 26-bit values!
In short, our first quick success for a long time.
After just a few hours of messing about, we've got a fully working RFID reading system.
In future posts, we'll look at writing our lookup table to an SD card and logging each time a card is swiped by writing a log file back to the card, making a completely PC-independent, embedded system!
No comments:
Post a Comment