树莓派复制MFRC522 门禁IC卡(支持block0写入,亲测可用)

1. 基本原理

友情提示:本篇文章只用于学习交流使用,请不要用于其它用途

我们总共需要三个组件:树莓派,MFRC522模块,IC卡

树莓派与MFRC522模块的交互通过SPI方式:树莓派首先通过"sudo raspi-config"命令打开SPI口,然后根据协议向模块特定寄存器读写数据,以此完成数据收发操作

MFRC522模块与IC卡是通过电磁感应无接触交互的:MFRC522模块内部会产生感应电磁场,IC卡内部线圈通过电磁感应获取电能,然后解析数据,最终完成IC卡内部存储器的数据读写操作

整个系统流程就是首先树莓派通过SPI接口向MFRC522模块发送数据和命令,MFRC522模块收到数据和命令后,产生电磁场,然后IC卡通过内部感应电圈获得电能和数据,解析数据后读写存储器,最终完成IC卡数据的读写

参考资料:https://download.csdn.net/download/chenwh_cn/12746663,附件中包含了IC卡调试工具,以及IC卡和MFRC522模块的数据手册,介绍了基本组成原理和内部存储器的功能,以及各种命令交互的数据协议格式,

2. 代码实现

a. SPI 依赖库的安装

下载源代码 https://github.com/lthiery/SPI-Py.git

然后进入SPI-Py目录执行:sudo python setup.py install

该库提供了3个API接口函数用以实现SPI通信:

  1. openSPI,用于打开SPI口,同时可以设置mode, speed, bits, delay等
  2. transfer,用于收发数据
  3. closeSPI,用于关闭SPI口

b. 驱动接口实现 MFRC522.py

"__"前缀为私有函数,"MFRC522_"前缀为公有函数,开头注释描述了树莓派与MFRC522模块的连线方式

# RC522-pin     Pi-pin
# 3.3V          1(3.3V)
# RST           22(GPIO25)
# GND           6(GND)
# IRQ           none
# MISO          21(GPIO9)
# MOSI          19(GPIO10)
# SCK           23(GPIO11)
# SDA           24(GPIO8)

import RPi.GPIO as GPIO
import spi
import signal
import time

class MFRC522:

    PCD_IDLE        = 0x00     # cancel current command
    PCD_AUTHENT     = 0x0E     # authent
    PCD_RECEIVE     = 0x08     # receive data
    PCD_TRANSMIT    = 0x04     # send data
    PCD_TRANSCEIVE  = 0x0C     # send & receive data
    PCD_RESETPHASE  = 0x0F     # reset
    PCD_CALCCRC     = 0x03     # CRC calculate

    PICC_REQIDL     = 0x26     # detect cards, not slepp
    PICC_REQALL     = 0x52     # detect cards, all 
    PICC_ANTICOLL   = 0x93     # anti collision
    PICC_SElECTTAG  = 0x93     # select card
    PICC_AUTHENT1A  = 0x60     # authent A
    PICC_AUTHENT1B  = 0x61     # authent B
    PICC_READ       = 0x30     # read block
    PICC_WRITE      = 0xA0     # write block
    PICC_DECREMENT  = 0xC0     # dec money
    PICC_INCREMENT  = 0xC1     # inc money
    PICC_RESTORE    = 0xC2     # send data to buf
    PICC_TRANSFER   = 0xB0     # save date from buf
    PICC_HALT       = 0x50     # idle status

    Reserved00      = 0x00     # register
    CommandReg      = 0x01
    CommIEnReg      = 0x02
    DivlEnReg       = 0x03
    CommIrqReg      = 0x04
    DivIrqReg       = 0x05
    ErrorReg        = 0x06
    Status1Reg      = 0x07
    Status2Reg      = 0x08
    FIFODataReg     = 0x09
    FIFOLevelReg    = 0x0A
    WaterLevelReg   = 0x0B
    ControlReg      = 0x0C
    BitFramingReg   = 0x0D
    CollReg         = 0x0E
    Reserved01      = 0x0F

    Reserved10      = 0x10
    ModeReg         = 0x11
    TxModeReg       = 0x12
    RxModeReg       = 0x13
    TxControlReg    = 0x14
    TxAutoReg       = 0x15
    TxSelReg        = 0x16
    RxSelReg        = 0x17
    RxThresholdReg  = 0x18
    DemodReg        = 0x19
    Reserved11      = 0x1A
    Reserved12      = 0x1B
    MifareReg       = 0x1C
    Reserved13      = 0x1D
    Reserved14      = 0x1E
    SerialSpeedReg  = 0x1F

    Reserved20        = 0x20
    CRCResultRegM     = 0x21
    CRCResultRegL     = 0x22
    Reserved21        = 0x23
    ModWidthReg       = 0x24
    Reserved22        = 0x25
    RFCfgReg          = 0x26
    GsNReg            = 0x27
    CWGsPReg          = 0x28
    ModGsPReg         = 0x29
    TModeReg          = 0x2A
    TPrescalerReg     = 0x2B
    TReloadRegH       = 0x2C
    TReloadRegL       = 0x2D
    TCounterValueRegH = 0x2E
    TCounterValueRegL = 0x2F

    Reserved30      = 0x30
    TestSel1Reg     = 0x31
    TestSel2Reg     = 0x32
    TestPinEnReg    = 0x33
    TestPinValueReg = 0x34
    TestBusReg      = 0x35
    AutoTestReg     = 0x36
    VersionReg      = 0x37
    AnalogTestReg   = 0x38
    TestDAC1Reg     = 0x39
    TestDAC2Reg     = 0x3A
    TestADCReg      = 0x3B
    Reserved31      = 0x3C
    Reserved32      = 0x3D
    Reserved33      = 0x3E
    Reserved34      = 0x3F

    NRSTPD          = 22
    MAX_LEN         = 18

    MI_OK           = 0
    MI_NOTAGERR     = 1
    MI_ERR          = 2
    MI_TIMEOUT      = 3


    def __init__(self, dev='/dev/spidev0.0', spd=1000000):
        self.dev0 = spi.openSPI(device=dev, speed=spd)
        GPIO.setmode(GPIO.BOARD)
        GPIO.setup(self.NRSTPD, GPIO.OUT)
        GPIO.output(self.NRSTPD, 1)
        self.MFRC522_Init()

    def __WriteReg(self, addr, val):
        spi.transfer(self.dev0, ((addr<<1)&0x7E, val))

    def __ReadReg(self, addr):
        val = spi.transfer(self.dev0, (((addr<<1)&0x7E) | 0x80, 0))
        return val[1]

    def __SetRegBitMask(self, reg, mask):
        tmp = self.__ReadReg(reg)
        self.__WriteReg(reg, tmp | mask)

    def __ClearRegBitMask(self, reg, mask):
        tmp = self.__ReadReg(reg)
        self.__WriteReg(reg, tmp & (~mask))

    def __ToCard(self, command, sendData):
        retStatus = self.MI_OK
        backData = []
        backLen = 0
        irqEn = 0x00
        waitIRq = 0x00
        lastBits = None
        n = 0
        i = 0

        if command == self.PCD_AUTHENT:
            irqEn   = 0x12
            waitIRq = 0x10
        if command == self.PCD_TRANSCEIVE:
            irqEn   = 0x77
            waitIRq = 0x30

        self.__WriteReg(self.CommIEnReg, irqEn|0x80)      # enable interupt
        self.__ClearRegBitMask(self.CommIrqReg, 0x80)     # clear interupt flags
        self.__SetRegBitMask(self.FIFOLevelReg, 0x80)     # init FIFO
        self.__WriteReg(self.CommandReg, self.PCD_IDLE)   # cancel current command

        for i in range(len(sendData)):
            self.__WriteReg(self.FIFODataReg, sendData[i])
        self.__WriteReg(self.CommandReg, command)

        if command == self.PCD_TRANSCEIVE:
            self.__SetRegBitMask(self.BitFramingReg, 0x80)

        i = 2000
        while True:
            n = self.__ReadReg(self.CommIrqReg)
            i -= 1
            if ~((i!=0) and ~(n&0x01) and ~(n&waitIRq)):
                break

        self.__ClearRegBitMask(self.BitFramingReg, 0x80)

        if i != 0:
            if (self.__ReadReg(self.ErrorReg) & 0x1B) == 0x00:
                if n & irqEn & 0x01:
                    retStatus = self.MI_NOTAGERR

                if command == self.PCD_TRANSCEIVE:
                    n = self.__ReadReg(self.FIFOLevelReg)
                    lastBits = self.__ReadReg(self.ControlReg) & 0x07
                    if lastBits != 0:
                        backLen = (n-1)*8 + lastBits
                    else:
                        backLen = n*8

                    if n == 0:
                        n = 1
                    if n > self.MAX_LEN:
                        n = self.MAX_LEN

                    for i in range(n):
                        backData.append(self.__ReadReg(self.FIFODataReg))
            else:
                retStatus = self.MI_ERR
        else:
            retStatus = self.MI_TIMEOUT

        return (retStatus, backData, backLen)

    def __CalulateCRC(self, indata):
        self.__ClearRegBitMask(self.DivIrqReg, 0x04)
        self.__SetRegBitMask(self.FIFOLevelReg, 0x80)
        for i in range(len(indata)):
            self.__WriteReg(self.FIFODataReg, indata[i])
        self.__WriteReg(self.CommandReg, self.PCD_CALCCRC)

        i = 255
        while True:
            n = self.__ReadReg(self.DivIrqReg)
            i -= 1
            if ~((i != 0) and ~(n&0x04)):
                break

        crc = []
        crc.append(self.__ReadReg(self.CRCResultRegL))
        crc.append(self.__ReadReg(self.CRCResultRegM))
        return crc

    def MFRC522_Init(self):
        self.MFRC522_Reset()
        self.__WriteReg(self.TModeReg, 0x8D)
        self.__WriteReg(self.TPrescalerReg, 0x3E)
        self.__WriteReg(self.TReloadRegL, 30)
        self.__WriteReg(self.TReloadRegH, 0)
        self.__WriteReg(self.TxAutoReg, 0x40)
        self.__WriteReg(self.ModeReg, 0x3D)
        self.MFRC522_AntennaOn()

    def MFRC522_Reset(self):
        # reg: 0x01
        # buf: 0x0F
        self.__WriteReg(self.CommandReg, self.PCD_RESETPHASE)

    def MFRC522_AntennaOn(self):
        # reg: 0x14
        # buf: 0bxxxxxx11
        temp = self.__ReadReg(self.TxControlReg)
        if not(temp & 0x03):
            self.__SetRegBitMask(self.TxControlReg, 0x03)

    def MFRC522_AntennaOff(self):
        # reg: 0x14
        # buf: 0bxxxxxx00
        self.__ClearRegBitMask(self.TxControlReg, 0x03)

    # function  : read block
    # parameter : blockAddr(0~63)
    # return    : retStatus
    #             backData[16]
    def MFRC522_ReadBolock(self, blockAddr):
        retStatus = self.MI_OK

        # cmd: 0x0c
        # buf: 0x30 blockAddr crc[2]
        buf = []
        buf.append(self.PICC_READ)
        buf.append(blockAddr)
        crc = self.__CalulateCRC(buf)
        buf.append(crc[0])
        buf.append(crc[1])
        (status, backData, backLen) = self.__ToCard(self.PCD_TRANSCEIVE, buf)
        if (status != self.MI_OK) or (backLen != 8*self.MAX_LEN):
            retStatus = self.MI_ERR

        return (retStatus, backData)

    # function  : write block
    # parameter : blockAddr(0~63)
    #             writeData[16]
    # return    : retStatus
    def MFRC522_WriteBlock(self, blockAddr, writeData):
        retStatus = self.MI_OK

        # cmd: 0x0c
        # buf: 0xA0 blockAddr crc[2]
        buf = []
        buf.append(self.PICC_WRITE)
        buf.append(blockAddr)
        crc = self.__CalulateCRC(buf)
        buf.append(crc[0])
        buf.append(crc[1])
        (status, backData, backLen) = self.__ToCard(self.PCD_TRANSCEIVE, buf)
        if (status != self.MI_OK) or ((backData[0]&0x0F) != 0x0A) or (backLen != 4):
            retStatus = self.MI_ERR

        if status == self.MI_OK:
            # cmd: 0x0c
            # buf: writeData[16] crc[2]
            buf2 = []
            for i in range(16):
                buf2.append(writeData[i])
            crc = self.__CalulateCRC(buf2)
            buf2.append(crc[0])
            buf2.append(crc[1])
            (status, backData, backLen) = self.__ToCard(self.PCD_TRANSCEIVE, buf2)
            if (status != self.MI_OK) or ((backData[0]&0x0F) != 0x0A) or (backLen != 4):
                retStatus = self.MI_ERR

        return retStatus

    # function  : detect card
    # parameter : reqMode: detect mode
    #               0x52 = detect all cards 
    #               0x26 = detect not sleep cards
    # return    : retStatus
    #             backData: card type(2 bytes)
    #               0x4400 = Mifare_UltraLight
    #               0x0400 = Mifare_One(S50)
    #               0x0200 = Mifare_One(S70)
    #               0x0800 = Mifare_Pro(X)
    #               0x4403 = Mifare_DESFire
    def MFRC522_Request(self, reqMode): 
        retStatus = self.MI_OK

        self.__WriteReg(self.BitFramingReg, 0x07)
        # cmd: 0x0c
        # buf: 0x26/0x52
        buf = []
        buf.append(reqMode)
        (status, backData, backLen) = self.__ToCard(self.PCD_TRANSCEIVE, buf)
        if (status != self.MI_OK) or (backLen != 0x10):
            retStatus = self.MI_ERR

        return (retStatus, backData)

    # function  : anticoll
    # parameter :
    # return    : retStatus
    #             backData(Uid, 4 bytes)
    def MFRC522_Anticoll(self):
        retStatus = self.MI_OK
        serNumCheck = 0

        self.__WriteReg(self.BitFramingReg, 0x00)
        # cmd: 0x0c
        # buf: 0x93 0x20
        buf = []
        buf.append(self.PICC_ANTICOLL)
        buf.append(0x20)
        (status, backData, backLen) = self.__ToCard(self.PCD_TRANSCEIVE, buf)
        if (status==self.MI_OK) and (len(backData)==5):
            for i in range(4):
                serNumCheck ^= backData[i]
            if serNumCheck != backData[4]:
                retStatus = self.MI_ERR
        else:
            retStatus = self.MI_ERR

        return (retStatus, backData)

    # function  : select card
    # parameter : Uid
    # return    : retStatus
    def MFRC522_Select(self, Uid):
        retStatus = self.MI_OK
        serNumCheck = 0

        # cmd: 0x0c
        # buf: 0x93 0x70 Uid[4] check crc[2]
        buf = []
        buf.append(self.PICC_SElECTTAG)
        buf.append(0x70)
        for i in range(4):
            buf.append(Uid[i])
            serNumCheck ^= Uid[i]
        buf.append(serNumCheck)
        crc = self.__CalulateCRC(buf)
        buf.append(crc[0])
        buf.append(crc[1])
        (status, backData, backLen) = self.__ToCard(self.PCD_TRANSCEIVE, buf)
        if (status != self.MI_OK) or (backLen != 0x18):
            retStatus = self.MI_ERR

        return retStatus

    # function  : auth sectorkey
    # parameter : authMode(PICC_AUTHENT1A/PICC_AUTHENT1B)
    #             blockAddr
    #             sectorkey(6 bytes)
    #             Uid(4 bytes)
    # return    : retStatus
    def MFRC522_Auth(self, authMode, blockAddr, sectorkey, Uid):
        retStatus = self.MI_OK

        # cmd: 0x0e
        # buf: authMode blockAddr sectorkey[6] Uid[4]
        buf = []
        buf.append(authMode)
        buf.append(blockAddr)
        for i in range(6):
            buf.append(sectorkey[i])
        for i in range(4):
            buf.append(Uid[i])
        (status, backData, backLen) = self.__ToCard(self.PCD_AUTHENT, buf)
        if (status != self.MI_OK) or not(self.__ReadReg(self.Status2Reg)&0x08):
            retStatus = self.MI_ERR

        return retStatus

    # function  : idle
    # parameter :
    # return    : retStatus
    def MFRC522_Halt(self):
        retStatus = self.MI_OK

        # cmd: 0x0c
        # buf: 0x50 0x00 crc[2]
        buf = []
        buf.append(self.PICC_HALT)
        buf.append(0)
        crc = self.__CalulateCRC(buf)
        buf.append(crc[0])
        buf.append(crc[1])
        (status, backData, backLen) = self.__ToCard(self.PCD_TRANSCEIVE, buf)
        retStatus = status

        return retStatus

    def MFRC522_WriteCmd40(self):
        retStatus = self.MI_OK

        self.__WriteReg(self.BitFramingReg, 0x07)
        # cmd: 0x0c
        # buf: 0x40
        buf = []
        buf.append(0x40)
        (status, backData, backLen) = self.__ToCard(self.PCD_TRANSCEIVE, buf)
        if (status != self.MI_OK) or (backData[0] != 0x0a):
            retStatus = status

        return retStatus

    def MFRC522_WriteCmd43(self):
        retStatus = self.MI_OK

        self.__WriteReg(self.BitFramingReg, 0x00)
        # cmd: 0x0c
        # buf: 0x43
        buf = []
        buf.append(0x43)
        (status, backData, backLen) = self.__ToCard(self.PCD_TRANSCEIVE, buf)
        if (status != self.MI_OK) or (backData[0] != 0x0a):
            retStatus = status

        return retStatus

    def MFRC522_StopCrypto1(self):
        self.__ClearRegBitMask(self.Status2Reg, 0x08)

    def MFRC522_CloseSPI(self):
        spi.closeSPI(self.dev0)

c. 复制IC卡数据实现 main.py

通过request, anticoll, select, auth, halt 等命令,首先从源卡中读取block数据,然后将数据写入到新卡对应的block中

注意:block0的数据写入与其它block的写入不同,还需要写入两个特殊的命令0x40和0x43, 参考:https://blog.csdn.net/baidu_34570497/article/details/79689778

#!/usr/bin/env python
# -*- coding: utf8 -*-

import MFRC522

rc = MFRC522.MFRC522()
# save src card data, then write the saved date to des card
dataBlock0 = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15]
sectorkey = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]

def read_block0():
    (status, tagType) = rc.MFRC522_Request(rc.PICC_REQIDL)
    if status != rc.MI_OK:
        print("MFRC522_Request error")
        return
    print("MFRC522_Request success, tagType: %#x %#x" %(tagType[0], tagType[1]))

    (status, Uid) = rc.MFRC522_Anticoll()
    if status != rc.MI_OK:
        print("MFRC522_Anticoll error")
        return
    print("MFRC522_Anticoll success, Uid: %#x %#x %#x %#x" %(Uid[0], Uid[1], Uid[2], Uid[3]))

    status = rc.MFRC522_Select(Uid)
    if status != rc.MI_OK:
        print("MFRC522_Select error")
        return
    print("MFRC522_Select success")

    status = rc.MFRC522_Auth(rc.PICC_AUTHENT1A, 1, sectorkey, Uid)
    if status != rc.MI_OK:
        print("MFRC522_Auth error")
        return
    print("MFRC522_Auth success")

    (status, dataBlock0) = rc.MFRC522_ReadBolock(0)
    if status != rc.MI_OK:
        print("MFRC522_ReadBolock error")
        return
    print("MFRC522_ReadBolock success, block0: %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x %#x" 
          %(dataBlock0[0], dataBlock0[1], dataBlock0[2], dataBlock0[3], dataBlock0[4], dataBlock0[5], dataBlock0[6], dataBlock0[7], 
            dataBlock0[8], dataBlock0[9], dataBlock0[10], dataBlock0[11], dataBlock0[12], dataBlock0[13], dataBlock0[14], dataBlock0[15]))

    rc.MFRC522_StopCrypto1()


def write_block0():
    (status, tagType) = rc.MFRC522_Request(rc.PICC_REQIDL)
    if status != rc.MI_OK:
        print("MFRC522_Request error")
        return
    print("MFRC522_Request success, tagType: %#x %#x" %(tagType[0], tagType[1]))

    (status, Uid) = rc.MFRC522_Anticoll()
    if status != rc.MI_OK:
        print("MFRC522_Anticoll error")
        return
    print("MFRC522_Anticoll success, Uid: %#x %#x %#x %#x" %(Uid[0], Uid[1], Uid[2], Uid[3]))

    status = rc.MFRC522_Select(Uid)
    if status != rc.MI_OK:
        print("MFRC522_Select error")
        return
    print("MFRC522_Select success")

    status = rc.MFRC522_Halt()
    print("MFRC522_Halt %d" %status)

    status = rc.MFRC522_WriteCmd40()
    if (status != rc.MI_OK):
        print("MFRC522_ToCard 0x40 error, status:%d" %status)
        return
    print("MFRC522_ToCard 0x40 success")

    status = rc.MFRC522_WriteCmd43()
    if (status != rc.MI_OK):
        print("MFRC522_ToCard 0x43 error, status:%d" %status)
        return
    print("MFRC522_ToCard 0x43 success")

    status = rc.MFRC522_WriteBlock(0, dataBlock0)
    if status != rc.MI_OK:
        print("MFRC522_WriteBlock error")
        return
    print("MFRC522_WriteBlock success")

    rc.MFRC522_StopCrypto1()  

def closeSPI():
    rc.MFRC522_CloseSPI()


if __name__ == '__main__':
    while True:
        action = input("Enter action r/w: ")
        if action == 'r':
            read_block0()
        elif action == 'w':
            write_block0()
        elif action == 'q':
            closeSPI()
            break
    print("exit procedure")

d. 运行结果

  1. 将原卡放到模块的感应区域,然后输入“r”,程序读取原卡block0数据
  2. 将新卡放到模块的感应区域,然后输入“w”,程序将读出的block0数据写入到新卡的block0
  3. 测试新卡门禁是否可用

注意:

调试过程中很可能会出现0x40和0x43写入失败的问题,定位发现是 MFRC522_Halt() 命令执行失败,排除掉其它因素后极有可能是使用的新卡不支持block0写入的,可以换个卡再试试

通常普通门禁只是读取IC卡的Uid进行校验,即block0的前4个字节,如果block0成功写入还是不能打开门禁,可能就是门禁系统还使用了其它数据进行校验,这时可以将原卡的所有block数据都读出来然后写入新卡

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值