;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; An ultra-simple Verichip cloner: we can behave either as a reader (to ;; get the ID of a legitimate tag), or as a simulated tag (to replay the ;; stored ID to a legitimate reader). ;; ;; The hardware is relatively stupid; I've made every compromise I could ;; think of to get the parts count down. ;; ;; This code is for MPASM 5.05, probably works with other versions though. ;; ;; Jonathan Westhues, Sep 2006 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; #include radix dec ;; leave BODEN off to get the standby battery life up (want a few years, ;; comparable to shelf life of the alkaline AAA cells) __config _CP_OFF & _HS_OSC & _PWRTE_ON & _WDT_OFF & _BODEN_OFF & _LVP_OFF ;; GPIO pin assignments on PORTA #define PORTA_READER_OUTPUT 0 #define PORTA_CARRIER_SENSE 1 ;; GPIO pin assignments on PORTB #define PORTB_LED_GREEN 0 #define PORTB_LED_WHITE 1 #define PORTB_DIVIDER_POWER 2 #define PORTB_COIL_DRIVER 3 #define PORTB_SWITCH_WHITE 4 #define PORTB_SWITCH_GREEN 5 ;; Convenience macros for btfss/btfsc; I find these quicker to read. ifset macro port, bit btfsc port, bit endm ifclear macro port, bit btfss port, bit endm ;; Wrapper macros to manipulate the two LEDs on the board. Used only for ;; user interface, nothing special. WhiteLedOn macro bcf PORTB, PORTB_LED_WHITE endm WhiteLedOff macro bsf PORTB, PORTB_LED_WHITE endm GreenLedOn macro bcf PORTB, PORTB_LED_GREEN endm GreenLedOff macro bsf PORTB, PORTB_LED_GREEN endm ;; Macros for time delays (cycle-counted busy waits). These are only ;; approximate. DebounceWait macro label movlw 60 movwf milliCount label Wait1Millisecond decfsz milliCount, f goto label endm Wait1Millisecond macro clrf microCount goto $ + 1 goto $ + 1 goto $ + 1 decfsz microCount, f goto $ - 4 endm ;; Wrappers macros to manipulate the PWM peripheral (Timer2/CCP), used to ;; divide the micro clock down to produce the transmitted carrier in ;; `reader' mode. We will use (10 MHz)/(4*(18+1)) = 132 kHz, for ~2% ;; error vs. desired 134 kHz, good enough. TurnOnPwmPeripheral macro banksel PR2 movlw 18 movwf PR2 ^ 0x80 banksel CCPR1L movlw 9 ; Pwm duty cycle 50% movwf CCPR1L movlw 0x0c ; Pwm mode, MSBs clear movwf CCP1CON bsf T2CON, 2 ; T2 on endm TurnOffPwmPeripheral macro clrf CCP1CON bcf T2CON, 2 ; T2 off endm ;; Variables in Bank 0. cblock 0x20 microCount bitCount cardId:64 milliCount cycleCount temp iterCount endc org 0 Reset goto Init ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; We get an interrupt whenever the user presses or releases a button with ;; interrupts on. The interrupt handler looks at the state of the pushbuttons, ;; and from this it jumps to the correct operating mode. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; org 4 Isr bcf STATUS, RP0 bcf INTCON, RBIF DebounceWait l12 ifclear PORTB, PORTB_SWITCH_GREEN goto switchGreenPressed ifclear PORTB, PORTB_SWITCH_WHITE goto switchWhitePressed ;; so neither is pressed, so go to sleep DebounceWait l6 goto SleepNSpin ;; White switch is for read mode. That mode is latched, and then exited with ;; a press of the green switch, so we must wait until they release the ;; white switch before entering that code. switchWhitePressed WhiteLedOn DebounceWait l0 awaitReleaseWhite ifclear PORTB, PORTB_SWITCH_GREEN goto bothSwitchesPressed ifclear PORTB, PORTB_SWITCH_WHITE goto awaitReleaseWhite DebounceWait l1 bcf INTCON, RBIF bsf INTCON, GIE ;; can break us out by pressing any other button goto GetIdFromCard ;; Green switch is for replay mode; that mode stays only as long as the ;; switch is held for, so jump straight in to that routine and let the edge ;; when the switch is released take us out. switchGreenPressed WhiteLedOff GreenLedOn DebounceWait l2 bcf INTCON, RBIF bsf INTCON, GIE goto TransmitCardId ;; Both switches means `load sample ID', in this case Annalee's. Then we ;; just wait for the button to be released, and go back to sleep. bothSwitchesPressed WhiteLedOn GreenLedOn DebounceWait l3 awaitReleaseBoth ifclear PORTB, PORTB_SWITCH_WHITE goto awaitReleaseBoth ifclear PORTB, PORTB_SWITCH_GREEN goto awaitReleaseBoth DebounceWait l4 goto writeIdToEeprom ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Where we end up after power-on. Load either a fixed ID from program ;; memory, or the ID that we have stored in EEPROM, and then go to sleep. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Init ;; load the stored ID from flash, or the one from the table if there's ;; none in flash bsf STATUS, RP0 clrf EEADR ^ 0x80 bsf EECON1 ^ 0x80, RD movf EEDATA ^ 0x80, w bcf STATUS, RP0 xorlw 0xff ifset STATUS, Z goto loadFixedId ; and this jumps to SleepNSpin when it's done ;; so there's a valid ID in there, somewhere; load it from flash movlw cardId movwf FSR movlw 64 movwf bitCount bsf STATUS, RP0 clrf EEADR ^ 0x80 bcf STATUS, RP0 loadIdFromFlash bsf STATUS, RP0 bsf EECON1 ^ 0x80, RD movf EEDATA ^ 0x80, w incf EEADR ^ 0x80, f bcf STATUS, RP0 movwf INDF incf FSR, f decfsz bitCount, f goto loadIdFromFlash ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Go to sleep, and wait for an interrupt to wake us up. First we should power ;; down anything that might waste current, though. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; SleepNSpin ; Turn on the pull-ups, which we need to operate the switches. banksel OPTION_REG bcf OPTION_REG ^ 0x80, NOT_RBPU ; Switches are inputs, all others outputs on PORTB. banksel TRISB movlw 0x30 movwf TRISB ^ 0x80 ; RA0 and RA1 are used as comparators, rest are unused, so drive them ; as outputs to avoid class A current in input buffers. movlw 0x03 movwf TRISA ^ 0x80 banksel PORTB clrf PORTA ; Configure comparators as off (since they draw 30 uA each, rather a ; lot of current). movlw 0x00 movwf CMCON ; Configure voltage reference as off (since it also draws current) banksel VRCON movlw 0x00 movwf VRCON ^ 0x80 banksel PORTB ; Drive LEDs HIGH (off), coil driver LOW (don't waste current in R6), ; voltage divider LOW (don't waste current in that), programming pins ; (which are N/C in normal operation) LOW. movlw 0x03 movwf PORTB TurnOffPwmPeripheral ; ISR forces us out, so must turn on interrupts bsf INTCON, RBIE bcf INTCON, RBIF bsf INTCON, GIE asleep sleep goto asleep ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Routines to transmit an ID; basically we count incident carrier cycles ;; using the comparator on PORTA_CARRIER_SENSE, and from that we determine ;; our timing as we clock out the stored ID over and over. ;; ;; This routine does not return; it exits only as a result of an interrupt. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; TransmitCardId ; Set up Vref to ground, since the sensed carrier comes in AC-coupled ; about that point. banksel VRCON movlw 0xa0 movwf VRCON ^ 0x80 ; Both comparators on, -inputs to RA0/RA1, +inputs to Vreg module banksel CMCON movlw 0x02 movwf CMCON ; Set the pin that drives the emitter followers LOW. The loop will touch ; only TRISB, not PORTB, so that pin will alternate between low impedance ; to ground and tri-state. bcf PORTB, PORTB_COIL_DRIVER spinTx movlw 64 movwf bitCount movlw cardId movwf FSR replayNibble variable i = 0 while i < 4 ;; First, we wait for 16 incident carrier cycles movlw 16 movwf cycleCount ifset CMCON, 7 goto $ - 1 ifclear CMCON, 7 goto $ - 1 decfsz cycleCount, f goto $ - 5 ;; Then, we set the coil driver pin according to the ID stored in memory. bsf STATUS, RP0 bsf TRISB ^ 0x80, PORTB_COIL_DRIVER ifclear INDF, i bcf TRISB ^ 0x80, PORTB_COIL_DRIVER bcf STATUS, RP0 variable i = i + 1 endw incf FSR, f decfsz bitCount, f goto replayNibble goto spinTx ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Routines to read the ID. We apply a ~134 kHz square wave to the coil driver ;; gates, using the PWM module to save ourselves the pain of counting ;; cycles. Then we wait for an edge after a long period of time, and we ;; assume that we just sync'd on the ID so we receive it. ;; ;; This is prone to error, though, especially at startup, so we read the ID ;; again and compare it to the one that we recorded. If they don't match then ;; we throw the stored ID away and start over. ;; ;; This routine will return if it thinks that it has read an ID, or exit ;; due to an interrupt. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; GetIdFromCard ; Set up Vref to Vdd/2, since the sensed carrier comes in AC-coupled ; about that point. banksel VRCON movlw 0xa0 movwf VRCON ^ 0x80 ; Both comparators on, -inputs to RA0/RA1, +inputs to Vreg module banksel CMCON movlw 0x02 movwf CMCON ; Let us minimize the substrate current injected through R9, by driving ; that pin as an output. banksel TRISA bcf TRISA ^ 0x80, 1 banksel PORTB bcf PORTB, PORTB_COIL_DRIVER TurnOnPwmPeripheral ; give the oscillator some time to settle (tuned circuit) DebounceWait l8 spinRx ; First, wait for an edge (any edge; we will replay the ID cyclically, ; so it doesn't matter where in the ID we get bit-sync). awaitEdgeHigh ifclear CMCON, 6 goto awaitEdgeHigh awaitEdgeLow ifset CMCON, 6 goto awaitEdgeLow ; Now, delay a little while so that we will sample the bit at the centre ; of the bit time, not at the edge. movlw 60 movwf microCount spinToMiddle decfsz microCount, f goto spinToMiddle ; Set up the area of memory in which we will store the ID. movlw cardId movwf FSR movlw 64 movwf bitCount nextBit ; This loop is unrolled, to four iterations per jump; this is because ; we want to store 256 bits in 64 bytes, so we must use four iterations variable i = 0 while i < 4 bcf INDF, i ifset CMCON, 6 bsf INDF, i GreenLedOn GreenLedOff movlw 98 movwf microCount decfsz microCount, f goto $ - 1 goto $ + 1 variable i = i + 1 endw goto $ + 1 goto $ + 1 incf FSR, f decfsz bitCount, f goto nextBit movlw 3 movwf iterCount checkIdManyTimes ; Now we have the ID; but since we don't know how to check the CRC or ; anything like that, we need some way to determine whether we've ; received a valid signal, or just noise. Do this by receiving the ID ; a second time. movlw cardId movwf FSR movlw 64 movwf bitCount nextBitCheck ; This loop is unrolled, to four iterations per jump; this is because ; we have stored 256 bits in 64 bytes. Check each bit to see that it ; is the same that we received last time. variable i = 0 while i < 4 movlw 0 ifset CMCON, 6 movlw (1<