【RFID】MFRC522射频读卡器控制程序

记录 2017-01-25 1 条评论 访问: 6,639 次

MFRC522 RFID 模块

连接树莓派读取卡片信息。本文涉及到MFRC522的驱动程序。

0x00 预备知识

MifareS50卡相关数据参数
C语言基础
树莓派
英文缩写
重点记住 PICC 是卡片的意思
PCD 是读卡器的意思
后面有完整的缩写列表

0x01 连接 MFRC522 和树莓派

abbr.jpeg

rc522 RFID模块

rc522.jpg


GPIO 接法

GPIO RC522接口
1 3.3v
6 GND
19 MOSI
21 MISO
22 RST
23 SCK
24 SDA

GPIO 图

rpi_gpio_layout.png


接好后是这样子的 IRQ 不连
connectToPi.jpg

0x02 驱动文件(向 rc522 寄存器写命令,读取相关寄存器的值)和上层函数(选卡、防冲突、认证、读写)

mfrc522.c 驱动封装,包含基本函数和上层函数

void writeMFRC522(unsigned char Address, unsigned char value);
void antennaOn(void);

//选卡函数
unsigned char selectCard(unsigned char *pSnr);

//读数据块函数,要提供块地址,和一个容器
unsigned char read(unsigned char blockAddr,char* strData);

//写数据块函数,要提供块地址和要写入的数据,注意,数组中每个元素是数据的 16 进制数。
//例如,strData[3] = 0x66,当然你愿意转化成十进制也可以 strData[3] = 102。反正就是一个数多少进制无所谓。
unsigned char write(unsigned char blockAddr, unsigned char *writeData);

//认证函数,要提供认证模式(用 keyA 还是 keyB 认证),要认证的块地址,密钥,uid
unsigned char auth(unsigned char auth_mode,unsigned char addr,unsigned char *pKey,unsigned char *pSnr);

void RFID_init()// rc522 射频读卡器的初始化,不深究了
{
    bcm2835_spi_begin();
 
    bcm2835_spi_setBitOrder(BCM2835_SPI_BIT_ORDER_MSBFIRST);
    bcm2835_spi_setDataMode(BCM2835_SPI_MODE0);
    bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_65536);
    bcm2835_spi_chipSelect(BCM2835_SPI_CS0);
    bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS0, LOW);
 
    writeMFRC522(CommandReg, PCD_RESETPHASE);
    writeMFRC522(TModeReg, 0x8D);
    writeMFRC522(TPrescalerReg, 0x3E);
    writeMFRC522(TReloadRegL, 30);
    writeMFRC522(TReloadRegH, 0);
    writeMFRC522(TxAutoReg, 0x40);
    writeMFRC522(ModeReg, 0x3D);
    antennaOn();
}

//向 rc522 的寄存器中写数(可以是命令码等)
//接受寄存器的地址和要写入的值
void writeMFRC522(unsigned char Address, unsigned char value)
{
    char buff[2];
    buff[0] = (char)((Address<<1)&0x7E);
    buff[1] = (char)value;
    
    bcm2835_spi_transfern(buff,2); 
}

//读取rc522 的寄存器中的数
//接受要读的数据块的地址
unsigned char readMFRC522(unsigned char Address)
{
    char buff[2];
    buff[0] = ((Address<<1)&0x7E)|0x80;
    bcm2835_spi_transfern(buff,2);
    return (unsigned char)buff[1];
}

void setBitMask(unsigned char reg, unsigned char mask)
{
    unsigned char tmp;
    tmp = readMFRC522(reg);
    writeMFRC522(reg, tmp | mask);
}

void clearBitMask(unsigned char reg, unsigned char mask)
{
    unsigned char tmp;
    tmp = readMFRC522(reg);
    writeMFRC522(reg, tmp & (~mask));
}

void antennaOn(void)//打开天线
{
    unsigned char temp;
    temp = readMFRC522(TxControlReg);
    if(!(temp & 0x03))
    {
        setBitMask(TxControlReg, 0x03);
    }
}

void antennaOff(void)//关闭天线
{
    unsigned char temp;
    temp = readMFRC522(TxControlReg);
    if(!(temp & 0x03))
    {
        clearBitMask(TxControlReg, 0x03);
    }
}

//计算 CRC, CRC 占两个字节
//要计算的序列。长度。输出 CRC 的位置,一般就是追加在原数组后面
void calculateCRC(unsigned char *pIndata, unsigned char len, unsigned char *pOutData)
{
    unsigned char i, n;
    clearBitMask(DivIrqReg, 0x04);
    setBitMask(FIFOLevelReg, 0x80);
    for (i=0; i<len;i++)
    {
        writeMFRC522(FIFODataReg, *(pIndata+i));
    }
    writeMFRC522(CommandReg, PCD_CALCCRC);
    i = 0xFF;
    do
    {
        n = readMFRC522(DivIrqReg);
        i--;
    }
    while ((i!=0) && !(n&0x04));
    pOutData[0] = readMFRC522(CRCResultRegL);//CRC占两个字节
    pOutData[1] = readMFRC522(CRCResultRegM);//CRC占两个字节!
}



//这个函数比较关键,是MFRC522对卡片的操作,认证读取写入等都通过它实现。
//本质是操作 FIFO(First In First Out) 寄存器。
//要发送给卡片的命令码。要发送给 FIFO 的内容,是一个特定数据结构的 char 数组,
//比如写入就是 写入的命令码 + 16 字节数据 + 2 字节 CRC。
//发送的字节数,一个 char 一个字节。返回给的 FIFO 的数据,需要一个容器。返回数据的**位**数(8 位一字节)
unsigned char MFRC522ToCard(unsigned char command, unsigned char *sendData, unsigned char sendLen, unsigned char *backData, unsigned int *backLen)
{
    unsigned char status = MI_ERR;
    unsigned char irqEn = 0x00;
    unsigned char waitIRq = 0x00;
    unsigned char lastBits;
    unsigned char n;
    unsigned int i;
    switch(command)
    {
        case PCD_AUTHENT:     //认证卡密
        {
            irqEn = 0x12;
            waitIRq = 0x10;
            break;
        }
        case PCD_TRANSCEIVE:  //发送FIFO中数据
        {
            irqEn = 0x77;
            waitIRq = 0x30;
            break;
        }
        default:
            break;
  }
    writeMFRC522(CommIEnReg, irqEn|0x80); //允许中断请求
    clearBitMask(CommIrqReg, 0x80);       //清除所有中断请求位
    setBitMask(FIFOLevelReg, 0x80);       //FlushBuffer=1, FIFO初始化
    writeMFRC522(CommandReg, PCD_IDLE);   //无动作,取消当前命令
    //向FIFO中写入数据
    for (i=0; i<sendLen;i++)
    {
        writeMFRC522(FIFODataReg, sendData[i]);
    }
    //执行命令
    writeMFRC522(CommandReg, command);
    if(command == PCD_TRANSCEIVE)
    setBitMask(BitFramingReg, 0x80);
    //等待接收数据完成
    i = 2000; //i根据时钟频率调整,操作M1卡最大等待时间25ms
    do
    {
        n = readMFRC522(CommIrqReg);
        i--;
    }
    while ((i!=0) && !(n&0x01) && !(n&waitIRq));
    clearBitMask(BitFramingReg, 0x80);
    if(i != 0)
    {
        if(!(readMFRC522(ErrorReg) & 0x1B))
        {
            status = MI_OK;
            if(n & irqEn & 0x01)
                status = MI_NOTAGERR;
            if(command == PCD_TRANSCEIVE)
            {
                n = readMFRC522(FIFOLevelReg);
                lastBits = readMFRC522(ControlReg) & 0x07;
                if(lastBits)
                    *backLen = (n-1)*8 + lastBits;
                else
                    *backLen = n*8;
                if(n == 0)
                    n = 1;
                if(n > MAX_LEN)
                    n = MAX_LEN;
                //读取FIFO中接收到的数据
                for (i=0; i<n;i++)
                    backData[i] = readMFRC522(FIFODataReg);
            }
        }
        else
            status = MI_ERR;
    }
    return status;
}

//寻卡函数。
//寻卡模式,0x52 是寻天线范围内所有卡。TagType用来存放卡片类型,如果寻到则放入到容器。
unsigned char findCard(unsigned char reqMode, unsigned char *TagType)
{
    unsigned char status;
    unsigned int backBits;
    writeMFRC522(BitFramingReg, 0x07);
    TagType[0] = reqMode;
    status = MFRC522ToCard(PCD_TRANSCEIVE, TagType, 1, TagType, &backBits);
    if((status != MI_OK) || (backBits != 0x10))
        status = MI_ERR;
    return status;
}

//防冲撞函数,这个过程可以获取到卡片 uid 并存入容器
unsigned char anticoll(unsigned char *serNum)
{
    unsigned char status;
    unsigned char i;
    unsigned char serNumCheck=0;
    unsigned int unLen;
    clearBitMask(Status2Reg, 0x08);
    clearBitMask(CollReg,0x80);
    writeMFRC522(BitFramingReg, 0x00);
    serNum[0] = PICC_ANTICOLL;
    serNum[1] = 0x20;
    status = MFRC522ToCard(PCD_TRANSCEIVE, serNum, 2, serNum, &unLen);
    if(status == MI_OK)
    {
        //校验卡序列号
        for (i=0; i<4; i++)
        {
            *(serNum+i)  = serNum[i];
            serNumCheck ^= serNum[i];
        }
        if(serNumCheck != serNum[i])
        {
            status = MI_ERR;
        }
    }
    setBitMask(CollReg, 0x80);
    return status;
}
 
//让卡片休眠
void RFID_halt()
{
    unsigned char status;
    unsigned int unLen;
    unsigned char buff[4];
    buff[0] = PICC_HALT;
    buff[1] = 0;
    calculateCRC(buff, 2, &buff[2]);
    status = MFRC522ToCard(PCD_TRANSCEIVE, buff, 4, buff,&unLen);
}


/***************************上层建筑*****************************************/

//写块函数,块地址0-63,要写的数据的数组指针为函数
unsigned char write(unsigned char blockAddr, unsigned char *writeData)
{
    unsigned char status;
    unsigned int recvBits;
    unsigned char i;
    unsigned char buff[18];

    buff[0] = PICC_WRITE;
    buff[1] = blockAddr;//块地址0-63
    calculateCRC(buff, 2, &buff[2]);
    /*发送指令*/
    status = MFRC522ToCard(PCD_TRANSCEIVE, buff, 4, buff, &recvBits);

    /*这里判断返回状态*/
    if ((status != MI_OK) || (recvBits != 4) || ((buff[0] & 0x0F) != 0x0A))
        status = MI_ERR;
    /*准备16byte数据*/
    if (status == MI_OK){
        for (i=0; i<16; i++) 
            buff[i] = *(writeData+i);
        /*计算校验位*/
        calculateCRC(buff, 16, &buff[16]);
        /*发送数据*/
        status = MFRC522ToCard(PCD_TRANSCEIVE, buff, 18, buff, &recvBits);
        if ((status != MI_OK) || (recvBits != 4) || ((buff[0] & 0x0F) != 0x0A))
            status = MI_ERR;
    }
    return status;
}

//读块函数
//块地址0-63,信息被读取后存放到的数组的指针为参数
unsigned char read(unsigned char blockAddr,char* strData) {
    unsigned char status;
    unsigned int recvBits;
    unsigned char i;
    unsigned char buff[18];

    buff[0]=PICC_READ;
    buff[1]=blockAddr;
    calculateCRC(buff,2,&buff[2]);
    status=MFRC522ToCard(PCD_TRANSCEIVE,buff,4,buff,&recvBits);
    if ((status != MI_OK))// || (recvBits!=0x90))
        //是否返回18字节数据
        // 0x90转化为10进制是144,144/8=18
        status=MI_ERR;
    if(status==MI_OK){
        for(i=0;i<16;i++)
            *(strData+i)=buff[i];
    }
    return status;
}

//选卡
//uid为参数
unsigned char selectCard(unsigned char *pSnr)
{
    unsigned char status;
    unsigned char i;
    unsigned int  unLen;
    unsigned char buff[18]; 

    buff[0] = PICC_ANTICOLL1;
    buff[1] = 0x70;
    buff[6] = 0;
    for (i=0; i<4; i++)
    {
        buff[i+2] = *(pSnr+i);
        buff[6]  ^= *(pSnr+i);
    }
    calculateCRC(buff,7,&buff[7]);
        // buff中的数据情况如下图
        // |PICC_ANTICOLL1(0x93)|0x70|UID bit|UID bit|UID bit|UID bit|0和每位UID异或的结果|CRC|CRC|

    clearBitMask(Status2Reg,0x08);

    status = MFRC522ToCard(PCD_TRANSCEIVE,buff,9,buff,&unLen);

    if ((status == MI_OK) && (unLen == 0x18))
    {   status = MI_OK;  }
    else
    {   status = MI_ERR;    }

    return status;
}

//认证函数
//要提供认证模式(用 keyA 还是 keyB 认证),要认证的块地址,密钥,uid
unsigned char auth(unsigned char auth_mode,unsigned char addr,unsigned char *pKey,unsigned char *pSnr)
{
    unsigned char status;
    unsigned int  unLen;
    unsigned char i,buff[18]; 

    buff[0] = auth_mode;
    buff[1] = addr;
    for (i=0; i<6; i++)
    {    
        buff[i+2] = *(pKey+i);   
    }
    for (i=0; i<6; i++)
    {    
        buff[i+8] = *(pSnr+i);   
    }

    status = MFRC522ToCard(PCD_AUTHENT,buff,12,buff,&unLen);
    if ((status != MI_OK) || (!(readMFRC522(Status2Reg) & 0x08)))
    {   
        status = MI_ERR;   
    }

    return status;
}

RFID.h 发送给 rc522 命令码的定义和 rc522 发送给卡片命令码的定义,每个常量对应一个命令码

#define MAX_LEN 18
//PCD 是读卡器的意思
//PICC 是卡片的意思
//参见预备知识里英文缩写表格
/////////////////////////////////////////////////////////////////////
//MF522命令字 //跟 rc522 通信时要发送的命令码,
//比如想让 rc522 发送 FIFO 里的数据给卡片就要用 PCD_TRANSCEIVE 作为参数。
//怎么给 FIFO 发送数据?好吧,这其实是函数层层调用的关系,我们并不需要特定函数给 FIFO 发送数据,
//一个函数就完成了给 FIFO 发送数据又把 FIFO 中数据发送给卡片的功能。对着相关函数源码强撸吧,少年。
/////////////////////////////////////////////////////////////////////
#define PCD_IDLE              0x00               //取消当前命令
#define PCD_AUTHENT           0x0E               //验证密钥
#define PCD_RECEIVE           0x08               //接收数据
#define PCD_TRANSMIT          0x04               //发送数据
#define PCD_TRANSCEIVE        0x0C               //发送并接收数据
#define PCD_RESETPHASE        0x0F               //复位
#define PCD_CALCCRC           0x03               //CRC计算
/////////////////////////////////////////////////////////////////////
//Mifare_One卡片命令字 
//读卡器跟卡片通信时发给卡片的命令码。比如想验证A密钥。
/////////////////////////////////////////////////////////////////////
#define PICC_REQIDL           0x26               //寻天线区内未进入休眠状态
#define PICC_REQALL           0x52               //寻天线区内全部卡
#define PICC_ANTICOLL         0x93            
#define PICC_ANTICOLL1        0x93               //防冲撞
#define PICC_ANTICOLL2        0x95               //防冲撞
#define PICC_AUTHENT1A        0x60               //验证A密钥
#define PICC_AUTHENT1B        0x61               //验证B密钥
#define PICC_READ             0x30               //读块
#define PICC_WRITE            0xA0               //写块
#define PICC_DECREMENT        0xC0               //扣款
#define PICC_INCREMENT        0xC1               //充值
#define PICC_RESTORE          0xC2               //调块数据到缓冲区
#define PICC_TRANSFER         0xB0               //保存缓冲区中数据
#define PICC_HALT             0x50               //休眠
/////////////////////////////////////////////////////////////////////
//MF522 FIFO长度定义
/////////////////////////////////////////////////////////////////////
#define DEF_FIFO_LENGTH       64                 //FIFO size=64byte
/////////////////////////////////////////////////////////////////////
//MF522寄存器定义
/////////////////////////////////////////////////////////////////////
// PAGE 0
#define     RFU00                 0x00   
#define     CommandReg            0x01   
#define     CommIEnReg            0x02   
#define     DivlEnReg             0x03   
#define     CommIrqReg            0x04   
#define     DivIrqReg             0x05
#define     ErrorReg              0x06   
#define     Status1Reg            0x07   
#define     Status2Reg            0x08   
#define     FIFODataReg           0x09
#define     FIFOLevelReg          0x0A
#define     WaterLevelReg         0x0B
#define     ControlReg            0x0C
#define     BitFramingReg         0x0D
#define     CollReg               0x0E
#define     RFU0F                 0x0F
// PAGE 1    
#define     RFU10                 0x10
#define     ModeReg               0x11
#define     TxModeReg             0x12
#define     RxModeReg             0x13
#define     TxControlReg          0x14
#define     TxAutoReg             0x15
#define     TxSelReg              0x16
#define     RxSelReg              0x17
#define     RxThresholdReg        0x18
#define     DemodReg              0x19
#define     RFU1A                 0x1A
#define     RFU1B                 0x1B
#define     MifareReg             0x1C
#define     RFU1D                 0x1D
#define     RFU1E                 0x1E
#define     SerialSpeedReg        0x1F
// PAGE 2   
#define     RFU20                 0x20 
#define     CRCResultRegM         0x21
#define     CRCResultRegL         0x22
#define     RFU23                 0x23
#define     ModWidthReg           0x24
#define     RFU25                 0x25
#define     RFCfgReg              0x26
#define     GsNReg                0x27
#define     CWGsCfgReg            0x28
#define     ModGsCfgReg           0x29
#define     TModeReg              0x2A
#define     TPrescalerReg         0x2B
#define     TReloadRegH           0x2C
#define     TReloadRegL           0x2D
#define     TCounterValueRegH     0x2E
#define     TCounterValueRegL     0x2F
// PAGE 3     
#define     RFU30                 0x30
#define     TestSel1Reg           0x31
#define     TestSel2Reg           0x32
#define     TestPinEnReg          0x33
#define     TestPinValueReg       0x34
#define     TestBusReg            0x35
#define     AutoTestReg           0x36
#define     VersionReg            0x37
#define     AnalogTestReg         0x38
#define     TestDAC1Reg           0x39 
#define     TestDAC2Reg           0x3A  
#define     TestADCReg            0x3B  
#define     RFU3C                 0x3C  
#define     RFU3D                 0x3D  
#define     RFU3E                 0x3E  
#define     RFU3F    0x3F
/////////////////////////////////////////////////////////////////////
//和MF522通讯时返回的错误代码
/////////////////////////////////////////////////////////////////////
#define MI_OK                          0
#define MI_NOTAGERR                    (-1)
#define MI_ERR                         (-2)

read.c main 函数

//如果对流程还有问题,可以看看 Data sheet 里面读卡器和卡片交互的过程,main 程序里的流程与之一致。
#include <stdio.h>
#include <bcm2835.h>
#include <stdlib.h>
#include <string.h>
#include "RFID.h"
#include "mfrc522.c"
int main(){

    int i,j,count;
    unsigned char command;
    unsigned char sendData;
    unsigned char sendLen;
    unsigned char backData;
    unsigned int backLen;

    unsigned char s;

    unsigned char id[10];

    unsigned char key[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};

    unsigned char uid[5]; //4字节卡序列号,第5字节为校验字节

    unsigned char str[MAX_LEN];

    unsigned char wData[16] = {'h','a','c','k','e','d',' ','b','y',' ','r','u','o'};

    int isTrue = 1;

    if (!bcm2835_init()) return -1;

    RFID_init();

    while(isTrue){

        if (findCard(0x52,&s) == MI_OK){

            if ( anticoll(id) == MI_OK){

                memcpy(uid,id,5);

                printf("CARD UID:");

                for(i = 0;i < 5;i++)

                    printf("%x",uid[i]);

                printf("\n");

            }else {

                printf("FindCard ERR.\n");

            }
            

            //select Card

            selectCard(uid);


            //auth
            for(j=0;j<64;j++) {
                if(auth(0x61,j,key,uid) == MI_OK){


                    //read data
                    if(read(j,str) == MI_OK){

                        printf("Hex:");

                        for(i = 0;i < 16;i++)

                            printf("%02x",str[i]);

                        printf("\n");

                //        printf("Data:%s\n",str);

                    }

                }
                else

                    printf("Auth faild.\n");
            }

        }
        RFID_halt();

    }

    bcm2835_spi_end();

    bcm2835_close();

    return 0;
}

0x03 测试读卡

test_reader.jpg

终端里编译执行

read-result.jpg

读卡成功!


除非注明,嗯VIEW文章均为原创,转载请以链接形式标明本文地址
本文地址:https://www.umview.com/rfid-mfrc522

本文由 Mark 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

只有地板了

  1. KAI
    KAI

    老師你好,想請問RC522模組腳位接線問題,為何您的SDA 以及 SCK是接到23 與 24接腳?
    而不是 腳為3 以及 5呢?
    RC522的模組如果用在msp430f5529的話該如何接呢?

添加新评论