For Review: Wiegand Reader Object
JonnyMac
Posts: 9,529
A friend wants to add a commercial RFID reader / keypad with Wiegand output to a project so I thought it would be fun to write a driver for him. It does work (I've compared to a Proxmark3) but I'm always happy to get a code review in case I looked past something in the stress of trying to get everything to work.
Thanks for any feedback you wish to offer ( on the code -- not on me! <grin> )
JonnyMac
Thanks for any feedback you wish to offer ( on the code -- not on me! <grin> )
JonnyMac

Comments
Updated 2018-11-26 for Proxmark-compatible hex string output
'' ================================================================================================= '' '' File....... jm_wiegand_reader.spin '' Purpose.... '' Author..... Jon "JonnyMac" McPhalen '' -- see below for terms of use '' E-mail..... '' Started.... '' Updated.... 26 NOV 2018 '' '' ================================================================================================= {{ Connections: Reader -------------- d0pin <-----[4.7K]----- DAT0 (green) d1pin <-----[4.7K]----- DAT1 (white) Vss <---------------- Ground (black) }} con { fixed io pins } RX1 = 31 { I } ' serial / programming TX1 = 30 { O } SDA1 = 29 { I/O } ' i2c / eeprom SCL1 = 28 { I/O } con { string storage } MAX_BIN = 64 MAX_HEX = 20 var long cog ' cog running driver long bitcount ' # bits in last capture long wiegand0 ' space for 64 bits long wiegand1 dat BinDigits byte 0[MAX_BIN] ' reserve string space HexDigits byte 0[MAX_HEX] HexTable byte "0123456789ABCDEF", 0 pub start(d0pin, d1pin, toms) '' Start Wiegand reader cog '' -- d0pin is DAT0 input (through 4.7K resistor) '' -- d1pin is DAT1 input (through 4.7K resistor) '' -- toms is timeout in milliseconds stop ' stop if running bitcount.byte[0] := d0pin ' compress pins bitcount.byte[1] := d1pin wiegand0 := clkfreq / 1000 * toms ' covert timeout millis to ticks cog := cognew(@entry, @bitcount) + 1 ' launch the cog if (cog) ' if successful repeat while (bitcount <> -1) ' let cog initialize return cog pub stop '' Stop Wiegand reader cog if (cog) ' running? cogstop(cog-1) ' yes, stop cog := 0 ' and mark stopped pub count '' Return bit count from last read return bitcount pub ready '' Return true after successful read return (bitcount > 0) pub enable longfill(@wiegand0, 0, 2) ' clear old bits bitcount := 0 ' clear the count, trigger bg cog pub raw(part) | idx '' Returns raw bits from last read if (part == 0) return wiegand0 else return wiegand1 pub bin_str | p_str, bitz, mask '' Returns binary string representation of raw bits '' -- first bit in is first character in string '' * matches format charts which show bits left-to-right bytefill(@BinDigits, 0, MAX_BIN) ' clear old string p_str := @BinDigits ' point to it bitz := bitcount ' make copy of count if (bitz > 32) ' get high bits (if present) mask := 1 << (bitz-33) repeat (bitz-32) if (mask & wiegand1) byte[p_str++] := "1" else byte[p_str++] := "0" mask >>= 1 bitz := 32 mask := 1 << (bitz-1) ' get low bits repeat bitz if (mask & wiegand0) byte[p_str++] := "1" else byte[p_str++] := "0" mask >>= 1 return @BinDigits dat { proxmark preable bits } ' adapted from: ' -- https://github.com/linklayer/BLEKey/blob/master/wiegand.c ProxPre word $000, $000, $000, $000, $000, $000 ' 44..39 word $000, $000, $003, $005, $009, $011 ' 38..33 word $021, $041, $081, $101, $201, $401 ' 32..27 word $801 ' 26 pub hex_str | wbits[2], pre, p_str, idx '' Returns Proxmark-compatible hex string representation of raw bits '' -- always 5 bytes (10 hex digits) longmove(@wbits, @wiegand0, 2) ' copy (for preamble) if ((bitcount => 26) and (bitcount =< 44)) ' HID card? pre := ProxPre[44-bitcount] ' get preable from table if (bitcount => 32) wbits[1] |= pre << (bitcount-32) else wbits[1] |= pre >> (32-bitcount) wbits[0] |= pre << bitcount bytefill(@HexDigits, 0, MAX_HEX) ' clear old string p_str := @HexDigits ' point to it repeat idx from 4 to 0 byte[p_str++] := HexTable[wbits.byte[idx] >> 4] ' high nib byte[p_str++] := HexTable[wbits.byte[idx] & $F] ' low nib return @HexDigits dat { card info } CardInfo byte 26, 24, 17, 16, 1 byte 35, 32, 21, 20, 1 byte 37, 35, 20, 19, 1 byte 99, 99, 99, 99, 99 ' end of table marker pub extract_card(p_fc, p_cc) | p_table, bc, bounds ' not tested '' Extract facility and card values from stream '' -- p_fc and p_cc are pointers to facility code and card code (longs) '' -- assumes good data from reader '' -- no parity checking p_table := @CardInfo ' point to start of table repeat bc := byte[p_table] if (bc == bitcount) ' found it bytemove(@bounds, ++p_table, 4) ' read boundaries long[p_fc] := extract_bits(bounds.byte[0], bounds.byte[1]) long[p_cc] := extract_bits(bounds.byte[2], bounds.byte[3]) return elseif (bc > MAX_BIN) ' end of table? long[p_fc] := 0 long[p_cc] := 0 return else p_table += 5 ' next line in table pub key_code '' Returns ASCII char of key pressed '' -- for readers with 3x4 kepad if (bitcount <> 4) ' validate bit count return "?" result := extract_bits(3, 0) ' get the key code case result ' convert to ASCII 0..9 : result += "0" 10 : result := "*" 11 : result := "#" other : result := "?" pri extract_bits(msb, lsb) | mask '' Extract bits from wiegand stream if (msb < 32) result := wiegand0 << (31-msb) >> (31+lsb-msb) else result := wiegand1 << (32-(msb-31)) >> (lsb-(msb-31)) result |= wiegand0 >> lsb dat { wiegand receiver } org 0 entry mov t1, par ' hub address of parameters rdlong t2, t1 ' read pins mov t3, t2 ' make a copy add t1, #4 ' bump pointer rdlong totix, t1 ' read timeout ticks and t2, #$3F ' isolate dat0 pin mov d0mask, #1 ' convert to mask shl d0mask, t2 andn dira, d0mask mov t2, t3 ' restore t2 for DAT1 shr t2, #8 ' get byte1 and t2, #$3F ' isolate dat1 pin mov d1mask, #1 ' convert to mask shl d1mask, t2 andn dira, d1mask mov dxmask, d0mask ' off mask (both pins) or dxmask, d1mask mov ctra, FREE_RUN ' configure timeout timer mov frqa, #1 mov phsa, #0 ' reset timeout mov t1, par ' alert hub we're ready add t1, #4 mov t2, #0 wrlong t2, t1 add t1, #4 wrlong t2, t1 neg t2, #1 wrlong t2, par trigger_hold rdlong t1, par ' wait for hub bitcount == 0 tjnz t1, #trigger_hold mov bcount, #0 mov wbitz0, #0 mov wbitz1, #0 check_timeout tjz bcount, #check_bit0 ' skip timeout if no bits yet cmp totix, phsa wc, wz ' check timout if_a jmp #check_bit0 ' okay, escape report_bits mov t1, par add t1, #4 wrlong wbitz0, t1 add t1, #4 wrlong wbitz1, t1 wrlong bcount, par jmp #trigger_hold ' back to top check_bit0 test d0mask, ina wc ' move DAT0 status to C if_c jmp #check_bit1 ' if idle, no bit mov t1, #0 ' else bit value is 0 jmp #collect_bit check_bit1 test d1mask, ina wc ' move DAT1 status to C if_c jmp #check_timeout mov t1, #1 collect_bit shl wbitz0, #1 wc or wbitz0, t1 shl wbitz1, #1 muxc wbitz1, #1 add bcount, #1 ' inc bit count mov phsa, #0 ' reset timeout wait_idle mov t1, ina ' snapshot ina and t1, dxmask ' isolate d0 and d1 cmp t1, dxmask wc, wz ' check for both idle if_ne jmp #wait_idle jmp #check_timeout ' back to top ' ------------------------------------------------------------------------------------------------- FREE_RUN long %11111 << 26 ' increment every clock d0mask res 1 ' pin mask for DAT0 d1mask res 1 ' pin mask for DAT1 dxmask res 1 ' mask for both pins totix res 1 ' ticks in timeout period phub res 1 ' pointer to bitbuf[0] hub res 1 ' working hub pointer bcount res 1 ' current bit count wbitz0 res 1 ' low 32 bits wbitz1 res 1 ' high 32 bits t1 res 1 ' work vars t2 res 1 t3 res 1 fit 496 dat { license } {{ Terms of Use: MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. }}Even stranger, I just tried to download it again so I could take a screen shot of the error message and it downloaded perfectly.