ZigBee案例笔记 -- RFID卡片读写(模拟饭卡)

RFID模拟饭卡应用

RFID(射频识别技术)

RFID,全称为"射频识别技术"(Radio Frequency Identification),是一种非接触式自动识别技术。

它由读写器和标签(也叫芯片)两个主要组成部分组成。读写器是RFID系统中的中心,是负责发射电磁波和接收标签信号的设备。读写器可以读取标签中存储的信息,也可以将信息写入标签中存储。标签(芯片)是RFID系统中最基本的元素,是实现物体识别的核心部分。它由射频芯片、天线和封装材料组成,是一种小巧、灵活、可靠的数据存储设备。标签中存储的信息可以是物品的ID、生产日期、生产厂家、价格等。

由于RFID技术具有自动化、高效、非接触等优点,它在物流、仓库管理、资产跟踪、身份识别等领域得到广泛应用,正在成为物联网时代的重要组成部分。

RFID系统的工作原理如下:当标签进入读写器的射频场时,标签内的射频芯片接收来自读写器的电磁波,并通过接收的能量激活芯片中的电路,使其发出回复信号。读写器接收到标签发送的信号后,对其进行解码,就可以读取标签中存储的信息。读写器还可以向标签中写入信息,实现信息的更新。

在RFID实验中常用的RFID读写器如下图所示。所用的芯片是RC522。

RFID通讯协议

ISO 14443是一种非接触式IC卡的通讯协议,由国际标准化组织(ISO)制定,广泛应用于支付、门禁、公共交通、身份认证等场景。

ISO 14443协议定义了RFID标签与读写器之间的近距离(一般在10cm以内)非接触式通信规范,标签内置天线,利用读写器发射的射频信号进行供电和数据通信。

目前ISO 14443共分为四部分规范:

  • ISO 14443-A:最常使用的协议,支持4位、7位、10位的唯一卡序列号,多用于银行卡、公交卡、门禁等领域。

  • ISO 14443-B:支持中、高频的工作频率,多用于读写器、智能卡等应用场景。

  • ISO 14443-C:只用于芯片的漏接触式接口规范,多用于电子钱包、金融安全等领域。

  • ISO 14443-D: 与ISO 14443-A和ISO 14443-B不同,该规范定义了一种数据格式而不是通信协议,它主要用于近距离无线供电和数据通信场景。

RFID发展历史

RFID直接继承了雷达的概念,并由此发展出一种生机勃勃的AIDC新技术------RFID技术。1948年哈里.斯托克曼发表的"利用反射功率的通讯"奠定了射频识别RFID的理论基础。在20世纪中,无线电技术的理论与应用研究是科学技术发展最重要的成就之一。RFID技术的发展可按10年期划分如下:

  • 1941~1950年:雷达的改进和应用催生了RFID技术,1948年奠定了RFID技术的理论基础。
  • 1951~1960年:早期RFID技术的探索阶段,主要处于实验室实验研究。
  • 1961~1970年:RFID技术的理论得到了发展,开始了一些应用尝试。
  • 1971~1980年:RFID技术与产品研发处于一个大发展时期,各种RFID技术测试得到加速。出现了一些最早的RFID应用。
  • 1981~1990年:RFID技术及产品进入商业应用阶段,各种规模应用开始出现。
  • 1991~2000年:RFID技术标准化问题日趋得到重视,RFID产品得到广泛采用,RFID产品逐渐成为人们生活中的一部分。
  • 2001~至今:标准化问题日趋为人们所重视,RFID产品种类更加丰富,有源电子标签、无源电子标签及半无源电子标签均得到发展,电子标签成本不断降低,规模应用行业扩大。RFID技术的理论得到丰富和完善。单芯片电子标签、多电子标签识读、无线可读可写、无源电子标签的远距离识别、适应高速移动物体的RFID正在成为现实。

RFID操作流程说明

案例中使用的RFID读写器是市面常用的RC522(如上述图片),引脚顺序基本是固定的,与ZigBee芯片的连接引脚如下表所示(ZigBee连接引脚并非固定,可按实际连接引脚而定)

CC2530引脚 RC522引脚
P1_4 RST
P1_5 MISO
P1_6 MOSI
P1_7 SCK
P2_0 SDA
3.3V 3.3V
GND GND

RFID卡片读写流程

RFID卡片一般的操作步骤为寻卡、防碰撞、选卡、卡密验证、读卡/写卡

RFID寻卡

首先看看寻卡的指令说明,长度为7,命令类型0x02,Cmd为'A'(0x41),默认数据信息为0x52请求检测范围内所有符合类型的卡片

根据不同寻找到的不同类型卡片返回的ATQ也不一样,在返回的数据帧中,卡片类型的2个字节,低字节在前,高字节在后,如下

驱动代码如下,参数1为寻卡方式,一般是0x52寻符合14443A标准的卡,参数2为存放返回的2个字节卡片类型的数组:

cpp 复制代码
/
//功    能:寻卡
//参数说明: req_code[IN]:寻卡方式
//                0x52 = 寻感应区内所有符合14443A标准的卡
//                0x26 = 寻未进入休眠状态的卡
//          pTagType[OUT]:卡片类型代码
//                0x4400 = Mifare_UltraLight
//                0x0400 = Mifare_One(S50)
//                0x0200 = Mifare_One(S70)
//                0x0800 = Mifare_Pro(X)
//                0x4403 = Mifare_DESFire
//返    回: 成功返回MI_OK
/
char PcdRequest(unsigned char req_code,unsigned char *pTagType)
{
   char status;  
//   uint i;
   unsigned int  unLen;
   unsigned char ucComMF522Buf[MAXRLEN]; 

   ClearBitMask(Status2Reg,0x08);	//清理指示MIFARECyptol单元接通以及所有卡的数据通信被加密的情况
   WriteRawRC(BitFramingReg,0x07);	//	发送的最后一个字节的 七位
   SetBitMask(TxControlReg,0x03);	//TX1,TX2管脚的输出信号传递经发送调制的13.56的能量载波信号

   ucComMF522Buf[0] = req_code;		//存入 卡片命令字

   status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,1,ucComMF522Buf,&unLen);	//寻卡    
   if ((status == MI_OK) && (unLen == 0x10))	//寻卡成功返回卡类型 
   {    
       *pTagType     = ucComMF522Buf[0];
       *(pTagType+1) = ucComMF522Buf[1];
   }
   else
   {   
		status = MI_ERR;
	}
   
   return status;
}

RFID防碰撞

防碰撞指令长度为8,命令类型0x02,Cmd(命令)为'B'(0x42),防碰撞等级默认为第一级防碰撞0x93(低字节在前,高字节在后发送)

RFID卡片应答,如果防碰撞指令发出成功收到应答,则可获取到卡片的唯一序列号,如图得到的4字节序列号0x8e6e8610,在传输过程中同样给是低字节在前,高字节在后

驱动代码如下,传参为存放序列号的4字节数组

cpp 复制代码
/
//功    能:防冲撞
//参数说明: pSnr[OUT]:卡片序列号,4字节
//返    回: 成功返回MI_OK
/  
char PcdAnticoll(unsigned char *pSnr)
{
    char status;
    unsigned char i,snr_check=0;
    unsigned int  unLen;
    unsigned char ucComMF522Buf[MAXRLEN]; 
    

    ClearBitMask(Status2Reg,0x08);		//清MFCryptol On位 只有成功执行MFAuthent命令后,该位才能置位
    WriteRawRC(BitFramingReg,0x00);		//清理寄存器 停止收发
    ClearBitMask(CollReg,0x80);			//清ValuesAfterColl所有接收的位在冲突后被清除
   
    ucComMF522Buf[0] = 0x93;	//卡片防冲突命令
    ucComMF522Buf[1] = 0x20;
   
    status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,2,ucComMF522Buf,&unLen);//与卡片通信
    if (status == MI_OK)		//通信成功
    {
    	for (i=0; i<4; i++)
         {   
             *(pSnr+i)  = ucComMF522Buf[i];			//读出UID
             snr_check ^= ucComMF522Buf[i];

         }
         if (snr_check != ucComMF522Buf[i])
         {   status = MI_ERR;    }
    }
    
    SetBitMask(CollReg,0x80);
    return status;
}

RFID选卡

选卡指令,长度11,命令类型0x02,Cmd(命令)为'C'(0x43),将上一个指令拿到的UID(卡片序列号)发出,进行选卡(读卡/写卡前的操作 - 选卡)

RFID卡片应答,如果选择的卡片类型是S50卡,则会收到ATQ为0x08的数据帧

驱动代码,传参为上一个指令读出来的4字节卡片序列号

cpp 复制代码
/
//功    能:选定卡片
//参数说明: pSnr[IN]:卡片序列号,4字节
//返    回: 成功返回MI_OK
/
char PcdSelect(unsigned char *pSnr)
{
    char status;
    unsigned char i;
    unsigned int  unLen;
    unsigned char ucComMF522Buf[MAXRLEN]; 
    
    ucComMF522Buf[0] = PICC_ANTICOLL1;
    ucComMF522Buf[1] = 0x70;
    ucComMF522Buf[6] = 0;
    for (i=0; i<4; i++)
    {
    	ucComMF522Buf[i+2] = *(pSnr+i);
    	ucComMF522Buf[6]  ^= *(pSnr+i);
    }
    CalulateCRC(ucComMF522Buf,7,&ucComMF522Buf[7]);
  
    ClearBitMask(Status2Reg,0x08);

    status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,9,ucComMF522Buf,&unLen);
    
    if ((status == MI_OK) && (unLen == 0x18))
    {   status = MI_OK;  }
    else
    {   status = MI_ERR;    }

    return status;
} 

RFID卡密验证

卡密验证指令长度18,命令类型0x02,Cmd(命令)为'F'(0x46),指令内容需要发送验证密钥A/B、卡片序列号、密钥(默认密钥为6个0xFF)、需要操作的块号

关于块号,以上图的S50卡来说,S50卡(RFID卡片)内部分为16个扇区,每个扇区为4块,总16个扇区 64块按绝对地址为0~63,每块大小为16字节。块0里面存放了厂商代码,已经固化,不可以更改。每个扇区的块3存放了该扇区块前三块的权限管理,建议不要对扇区块3进行操作。结构如下图所示

每个扇区的块3为控制块,包括了密码A(6字节)、存取控制(4字节)、密码B(6字节),结构如下

cpp 复制代码
A0A1A2A3A4A5   FF078069   B0B1B2B3B4B5

注意:新卡默认出厂的块密码都是0xFF 0xFF 0xFF 0xFF 0xFF 0xFF,不建议修改(RFID有修改卡密的指令),毕竟忘了某个块的密码相当于这个块已经失去操作功能了

RFID卡片应答,卡密验证成功返回0,失败返回其他

驱动代码,传参A/B密钥、块地址(块号)、卡密、RFID序列号

cpp 复制代码
/
//功    能:验证卡片密码
//参数说明: auth_mode[IN]: 密码验证模式
//                 0x60 = 验证A密钥
//                 0x61 = 验证B密钥 
//          addr[IN]:块地址
//          pKey[IN]:密码
//          pSnr[IN]:卡片序列号,4字节
//返    回: 成功返回MI_OK
/               
char PcdAuthState(unsigned char auth_mode,unsigned char addr,unsigned char *pKey,unsigned char *pSnr)
{
    char status;
    unsigned int  unLen;
    unsigned char i,ucComMF522Buf[MAXRLEN]; 

    ucComMF522Buf[0] = auth_mode;
    ucComMF522Buf[1] = addr;
    for (i=0; i<6; i++)
    {    ucComMF522Buf[i+2] = *(pKey+i);   }
    for (i=0; i<6; i++)
    {    ucComMF522Buf[i+8] = *(pSnr+i);   }
 //   memcpy(&ucComMF522Buf[2], pKey, 6); 
 //   memcpy(&ucComMF522Buf[8], pSnr, 4); 
    
    status = PcdComMF522(PCD_AUTHENT,ucComMF522Buf,12,ucComMF522Buf,&unLen);
    if ((status != MI_OK) || (!(ReadRawRC(Status2Reg) & 0x08)))
    {   status = MI_ERR;   }
    
    return status;
}

RFID读卡

读卡指令长度7,命令类型0x02,Cmd(命令)为'G'(0x47),指令内容为指定要读的块号(S50卡块号范围为0 - 63)

RFID卡片应答,读卡成功返回对应卡块号保存的16字节数据

驱动代码,传参为卡片块地址(块号)

cpp 复制代码
/
//功    能:读取M1卡一块数据
//参数说明: addr[IN]:块地址
//          pData[OUT]:读出的数据,16字节
//返    回: 成功返回MI_OK
/ 
char PcdRead(unsigned char addr,unsigned char *pData)
{
    char status;
    unsigned int  unLen;
    unsigned char i,ucComMF522Buf[MAXRLEN]; 

    ucComMF522Buf[0] = PICC_READ;
    ucComMF522Buf[1] = addr;
    CalulateCRC(ucComMF522Buf,2,&ucComMF522Buf[2]);
   
    status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,4,ucComMF522Buf,&unLen);
    if ((status == MI_OK) && (unLen == 0x90))
 //   {   memcpy(pData, ucComMF522Buf, 16);   }
    {
        for (i=0; i<16; i++)
        {    *(pData+i) = ucComMF522Buf[i];   }
    }
    else
    {   status = MI_ERR;   }
    
    return status;
}

RFID写卡

写卡指令长度23,命令类型0x02,Cmd(命令)为'H'(0x48),命令内容为要写入的块号及16字节数据

RFID卡片应答,写入成功返回0,失败返回其他

驱动代码,传参为1字节块号,16字节写入的数据

cpp 复制代码
/
//功    能:写数据到M1卡一块
//参数说明: addr[IN]:块地址
//          pData[IN]:写入的数据,16字节
//返    回: 成功返回MI_OK
/                  
char PcdWrite(unsigned char addr,unsigned char *pData)
{
    char status;
    unsigned int  unLen;
    unsigned char i,ucComMF522Buf[MAXRLEN]; 
    
    ucComMF522Buf[0] = PICC_WRITE;
    ucComMF522Buf[1] = addr;
    CalulateCRC(ucComMF522Buf,2,&ucComMF522Buf[2]);
 
    status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,4,ucComMF522Buf,&unLen);

    if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
    {   status = MI_ERR;   }
        
    if (status == MI_OK)
    {
        //memcpy(ucComMF522Buf, pData, 16);
        for (i=0; i<16; i++)
        {    ucComMF522Buf[i] = *(pData+i);   }
        CalulateCRC(ucComMF522Buf,16,&ucComMF522Buf[16]);

        status = PcdComMF522(PCD_TRANSCEIVE,ucComMF522Buf,18,ucComMF522Buf,&unLen);
        if ((status != MI_OK) || (unLen != 4) || ((ucComMF522Buf[0] & 0x0F) != 0x0A))
        {   status = MI_ERR;   }
    } 
    return status;
}

读写数据流程

S50卡片读写数据流程图

RFID饭卡模拟案例

驱动代码

案例使用S50卡进行块数据写入读出和串口打印,主要驱动代码如下(操作部分的代码在上面),SPI连接引脚在variable.h中定义

cpp 复制代码
#include"variable.h"
#include"rc522.h"
#include"UART.h"

void SPIWriteByte(uchar infor)
{
    ... 
}

unsigned char SPIReadByte()
{
  ...
}

/
//功    能:读RC632寄存器
//参数说明:Address[IN]:寄存器地址
//返    回:读出的值
/
unsigned char ReadRawRC(unsigned char Address)
{
    ...
}
/
//功    能:写RC632寄存器
//参数说明:Address[IN]:寄存器地址
//          value[IN]:写入的值
/
void WriteRawRC(unsigned char Address, unsigned char value)
{  
    ...
}

/
//功    能:置RC522寄存器位
//参数说明:reg[IN]:寄存器地址
//          mask[IN]:置位值
/
void SetBitMask(unsigned char reg,unsigned char mask)  
{
    ...
}

/
//功    能:清RC522寄存器位
//参数说明:reg[IN]:寄存器地址
//          mask[IN]:清位值
/
void ClearBitMask(unsigned char reg,unsigned char mask)  
{
    ...
} 

/
//开启天线  
//每次启动或关闭天险发射之间应至少有1ms的间隔
/
void PcdAntennaOn(void)
{
    ...
}

/
//关闭天线
/
void PcdAntennaOff(void)
{
  ...
}


/
//功    能:复位RC522
//返    回: 成功返回MI_OK
/
void PcdReset(void)
{
	...
}


//
//设置RC632的工作方式 
//
void M500PcdConfigISOType(unsigned char type)
{
   ...
}

/
//功    能:通过RC522和ISO14443卡通讯
//参数说明:Command[IN]:RC522命令字
//          pInData[IN]:通过RC522发送到卡片的数据
//          InLenByte[IN]:发送数据的字节长度
//          pOutData[OUT]:接收到的卡片返回数据
//          *pOutLenBit[OUT]:返回数据的位长度
/
char PcdComMF522(unsigned char Command, 		//RC522命令字
                 unsigned char *pInData, 		//通过RC522发送到卡片的数据
                 unsigned char InLenByte,		//发送数据的字节长度
                 unsigned char *pOutData, 		//接收到的卡片返回数据
                 unsigned int  *pOutLenBit)		//返回数据的位长度
{
    ...
}

/
//功    能:寻卡
//参数说明: req_code[IN]:寻卡方式
//                0x52 = 寻感应区内所有符合14443A标准的卡
//                0x26 = 寻未进入休眠状态的卡
//          pTagType[OUT]:卡片类型代码
//                0x4400 = Mifare_UltraLight
//                0x0400 = Mifare_One(S50)
//                0x0200 = Mifare_One(S70)
//                0x0800 = Mifare_Pro(X)
//                0x4403 = Mifare_DESFire
//返    回: 成功返回MI_OK
/
char PcdRequest(unsigned char req_code,unsigned char *pTagType)
{
   ...
}

/
//功    能:防冲撞
//参数说明: pSnr[OUT]:卡片序列号,4字节
//返    回: 成功返回MI_OK
/  
char PcdAnticoll(unsigned char *pSnr)
{
    ...
}
/
//用MF522计算CRC16函数
/
void CalulateCRC(unsigned char *pIndata,unsigned char len,unsigned char *pOutData)
{
    ...
}
/
//功    能:选定卡片
//参数说明: pSnr[IN]:卡片序列号,4字节
//返    回: 成功返回MI_OK
/
char PcdSelect(unsigned char *pSnr)
{
    ...
}

/
//功    能:验证卡片密码
//参数说明: auth_mode[IN]: 密码验证模式
//                 0x60 = 验证A密钥
//                 0x61 = 验证B密钥 
//          addr[IN]:块地址
//          pKey[IN]:密码
//          pSnr[IN]:卡片序列号,4字节
//返    回: 成功返回MI_OK
/               
char PcdAuthState(unsigned char auth_mode,unsigned char addr,unsigned char *pKey,unsigned char *pSnr)
{
    ...
}

/
//功    能:写数据到M1卡一块
//参数说明: addr[IN]:块地址
//          pData[IN]:写入的数据,16字节
//返    回: 成功返回MI_OK
/                  
char PcdWrite(unsigned char addr,unsigned char *pData)
{
    ...
}
/
//功    能:读取M1卡一块数据
//参数说明: addr[IN]:块地址
//          pData[OUT]:读出的数据,16字节
//返    回: 成功返回MI_OK
/ 
char PcdRead(unsigned char addr,unsigned char *pData)
{
    ...
}

/
//功    能:命令卡片进入休眠状态
//返    回: 成功返回MI_OK
/
char PcdHalt(void)
{
...
}


void IC_CMT(uchar *UID,uchar *KEY,uchar RW,char *Dat)
{
  ...
}

串口协议

代码中通过串口发送协议指令来对RFID卡片进行操作,串口通讯协议帧组成如下

  • 寻卡指令 0x02 -> RFID协议命令类型 0x02(ISO14443A 类命令 CmdType = 2)
  • 寻卡方式 0x52 -> RFID寻卡模式 0x52 (请求天线范围内所有的卡)
  • 块读写指令
    -- 00:读块数据(读卡,读取饭卡金额)
    -- 01:块数据加(写卡,饭卡充值)
    -- 02:块数据减(写卡,饭卡扣费)
  • 块地址 XX (块号,范围 0 - 63)
  • 数据 XX (需要写入的数据 - 饭卡充值或扣钱金额,读卡时默认为00)

当串口收到数据时,进行单字节校验,将串口传输数据保存在数组RevBuffer中

cpp 复制代码
#pragma vector = URX0_VECTOR
 __interrupt void UART0_ISR(void)
{
  uchar tmp;
  URX0IF = 0;               //清除中断
  tmp = U0DBUF;            //接收数据

  // 头校验
  if((tmp == PACK_HEAD) && (PackFlag == HEAD_VERIFY))
  {
    count=0;
    RevBuffer[count++]=tmp;
    PackFlag = CMD_VERIFY;
  }
  // 指令校验
  else if((tmp == 0x02) && (PackFlag == CMD_VERIFY))
  {
    RevBuffer[count++]=tmp;
    PackFlag = TYPE_REQUEST;
  }
  // 无线平台校验(ZigBee)
  else if((tmp == 0x52) && (PackFlag == TYPE_REQUEST))
  {
    RevBuffer[count++]=tmp;
    PackFlag = ORDER_RECE;
  }
  // 模块类型与buff接收
  else if((count < sizeof(RevBuffer)) && (PackFlag == ORDER_RECE))
  {
    RevBuffer[count++]=tmp;
    
    if(count == sizeof(RevBuffer))
    PackFlag = HEAD_VERIFY;
  }
}

饭卡操作

在main函数中初始化RFID引脚并while循环RFID卡片靠近检测

cpp 复制代码
void main()
{
  Initial();
  PcdReset();
  M500PcdConfigISOType('A');  //设置工作方式
  
  while(1)
  {
    iccardcode();             //IC卡检测
  }
}

iccardcode()函数中,变量cmd获取串口指令进行RFID操作,当RevBuffer[1]为2时(收到串口指令),进行寻卡操作,寻卡成功(有RFID卡片移动到检测范围内)则修改RevBuffer[1]值,准备进入下一步防碰撞操作,在两次寻卡失败后串口打印信息,清空串口接收数组,退出switch

cpp 复制代码
void iccardcode()
{	     
  	unsigned char cmd;
	unsigned char status;
	
	cmd = RevBuffer[1];
	switch(cmd)
 	{
		...
		case 2: // Request 寻卡
			status= PcdRequest(RevBuffer[2],&RevBuffer[3]);
			if(status != MI_OK)
			{
              status= PcdRequest(RevBuffer[2],&RevBuffer[3]);
              if(status != MI_OK)				
              {
                 // 寻卡失败 退出
                  UartSend_String("PcdRequest Wrong !!!",sizeof("PcdRequest Wrong !!!"));
                  memset(RevBuffer,0,sizeof(RevBuffer));
                  break;
              }
			}  
			RevBuffer[1]=3;	
			RevBuffer[2]=status;
			break;
...
	}
}

成功寻卡后RevBuffer[1]值为3,调用PcdAnticoll()函数开始防碰撞操作,防碰撞操作成功时保存RFID序列号,修改RevBuffer[1]值,准备进入下一步选卡操作,通过串口16进制转ASC码打印,操作失败时串口打印错误信息,清空串口数组并退出

cpp 复制代码
void iccardcode()
{	     
  	unsigned char cmd;
	unsigned char status;
	
	cmd = RevBuffer[1];
	switch(cmd)
 	{
 		...
		case 3: // 防冲突 读卡的系列号 MLastSelectedSnr
			status = PcdAnticoll(&RevBuffer[3]);
			if(status != MI_OK)
			{
             // 防碰撞失败 退出
             UartSend_String("PcdAnticoll Wrong !!!",sizeof("PcdAnticoll Wrong !!!"));
             memset(RevBuffer,0,sizeof(RevBuffer));
			  break;
			}
            // 保存卡片ID
			memcpy(MLastSelectedSnr,&RevBuffer[3],4);

			RevBuffer[1]=4;
			RevBuffer[2]=status;
                        
            // 串口打印卡片ID
          	UartSend_String("ID: ",4);   
          	/****16进制转ASC码********/
          	for(uchar i=0;i<4;i++)
          	{
              Card_Id[i*2]=asc_16[RevBuffer[i+3]/16];
              Card_Id[i*2+1]=asc_16[RevBuffer[i+3]%16];        
          	}  
            UartSend_String(Card_Id,8); 
            UartSend('\t');
			break;
		...
	}
}

当RevBuffer[1]值为4时,开始进行RFID选卡,选择上一步保存的RFID序列号,选择成功时修改RevBuffer[1]值为5准备下一步验证卡密,选卡失败则串口打印错误信息,清空串口数组,退出

cpp 复制代码
void iccardcode()
{	     
  	unsigned char cmd;
	unsigned char status;
	
	cmd = RevBuffer[1];
	switch(cmd)
 	{
 		...
		case 4:	// 选择卡 Select Card
			status=PcdSelect(MLastSelectedSnr);
			if(status!=MI_OK)
			{
             // 选卡失败 退出
              UartSend_String("PcdSelect Wrong !!!",sizeof("PcdSelect Wrong !!!"));
              memset(RevBuffer,0,sizeof(RevBuffer));
			  break;
			}
			RevBuffer[1]=5;
			RevBuffer[2]=status;			
			break;
		...
	}
}

当RevBuffer[1]的值为5时,进行卡密验证,默认密码是6个0xFF。卡密验证成功后判断RevBuffer[7]是否为0,RevBuffer[7]为0将RevBuffer[1]修改为8(读卡),RevBuffer[7]为其他值则将RevBuffer[1]修改为9(写卡)。密码验证失败时串口打印错误信息,清空串口数组,退出

cpp 复制代码
uchar DefaultKey[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

void iccardcode()
{	     
  	unsigned char cmd;
	unsigned char status;
	
	cmd = RevBuffer[1];
	switch(cmd)
 	{
		...
		case 5: // Key loading into the MF RC500's EEPROM
          status = PcdAuthState(0x60, RevBuffer[8], DefaultKey, MLastSelectedSnr);// 校验卡密码
          if(status!=MI_OK)
		  {
             UartSend_String("PcdAuthState Wrong !!!",sizeof("PcdAuthState Wrong !!!"));
             memset(RevBuffer,0,sizeof(RevBuffer));
             break;
          }
                        
          // 数据查询/数据修改 判断
		  if(RevBuffer[7] == 0x00)
             RevBuffer[1]=8;
          else
             RevBuffer[1]=9;
                        
		  RevBuffer[2]=status;			
		  break;
		...
	}
}

当RevBuffer[1]的值为8时,进行RFID读卡操作,当读卡成功时,保存当前所读取的块数据,串口打印块数据(饭卡卡内金额),清除串口数组并退出;当读卡失败时,串口打印错误信息,清除串口数组并退出

cpp 复制代码
void iccardcode()
{	     
  	unsigned char cmd;
	unsigned char status;
	
	cmd = RevBuffer[1];
	switch(cmd)
 	{
		...
		case 8:     // Read the mifare card
		            // 读卡
			status=PcdRead(RevBuffer[8],&CValue[0]);
			if(status==MI_OK)
			{
            	// 保存块数据
            	memcpy(LastCValue,&CValue[0],1);
                          
            	// 将块数据转换成数字字符打印
            	UartSend_String("Card Value: ",12);
            	...
                          
            	// 清空串口数组,退出
            	memset(RevBuffer,0,sizeof(RevBuffer));
            	break;
          	}
			else
			{
            	// 读卡失败 退出
            	UartSend_String("PcdRead Wrong !!!\n",sizeof("PcdRead Wrong !!!\n"));
            	memset(RevBuffer,0,sizeof(RevBuffer));
            	break;
          	}	
			break;
		...
	}
}

当RevBuffer[1]的值为9时,进行RFID写卡操作,块数据大小为16字节,在案例中只用了第1个字节作为数据存储,而且设定数据范围是0~200,当写卡导致块数据超过设定范围时,串口打印报错,清除串口数组并退出

cpp 复制代码
#define MAX_VALUE 0xC8
#define MIX_VALUE 0x00

void iccardcode()
{	     
  	unsigned char cmd;
	unsigned char status;
	
	cmd = RevBuffer[1];
	switch(cmd)
 	{
		...
		case 9: // Write the mifare card
		        // 写卡  
           if((RevBuffer[7] == 0x01) && ((RevBuffer[9] + LastCValue[0]) > MAX_VALUE))
           {
             // 充值金额超过设定上限(200) 报错 退出
             UartSend_String("error,more than 200\n",sizeof("error,more than 200\n"));
             memset(RevBuffer,0,sizeof(RevBuffer));
             break;
           }
           if((RevBuffer[7] == 0x02) && ((LastCValue[0] - RevBuffer[9]) < MIX_VALUE))
           {
               // 卡内金额不足以扣除(<0) 报错 退出
               UartSend_String("error,less than 0\n",sizeof("error,less than 0\n"));
               memset(RevBuffer,0,sizeof(RevBuffer));
               break;
           }
		...
	}
}

当块数据修改在允许范围时,串口打印块数据变动内容,计算块数据更改后的值写入RevBuffer[9],修改RevBuffer[1]为8,将块数据通过串口打印出来

cpp 复制代码
void iccardcode()
{	     
  	unsigned char cmd;
	unsigned char status;
	
	cmd = RevBuffer[1];
	switch(cmd)
 	{
		...
		case 9: // Write the mifare card
		        // 写卡 
			... //(判断是否超范围)
			if(RevBuffer[7] == 0x01)
            {
                 // 打印充值数值
                 UartSend_String("add ",4);
                 ... //串口打印充值的金额
                
                 // 更新块数据(卡内金额)进行写入
                 RevBuffer[9] = LastCValue[0] + RevBuffer[9];
                 status=PcdWrite(RevBuffer[8],&RevBuffer[9]);
            }
            else if(RevBuffer[7] == 0x02)
            {
                  // 打印扣除数值
                  UartSend_String("deduct ",7);
                  ... //串口打印扣除的金额数
                          
                  // 更新块数据(卡内金额)进行写入
                  RevBuffer[9] = LastCValue[0] - RevBuffer[9];
                  status=PcdWrite(RevBuffer[8],&RevBuffer[9]);
                }
				RevBuffer[1]=8;
				RevBuffer[2]=status;			
				break;
		...
	}
}

案例结果

程序正常运行后,打开串口调试助手,修改波特率为115200,其他默认,打开串口,根据RFID实验的串口通讯协议,主要的操作内容为后3个字节,针对某个块地址的数据读写(注意块0及每个扇区的块3都不可进行操作)

饭卡余额查询(块数据读取),案例中操作的块为0x10,即块号16,扇区5的块0,块数据查询的指令如下

cpp 复制代码
FE 02 52 00 00 00 00 00 10 00

读卡时需要将RFID卡片置于读卡器上方1cm处,选择串口发送助手发送格式为16进制发送,发送查询指令可以收到RFID的ID卡号及指定块保存的饭卡余额数据

RFID写卡则分为给饭卡充值或饭卡扣费,串口的读写指令01为给饭卡充值,每次写入都能从串口打印看到充入数值和卡内当前金额,如充入金额的结果会超过设定的200,则会进行报错提示,充值指令如下

cpp 复制代码
FE 02 52 00 00 00 00 01 10 1E

实验结果如图

串口读写指令02为饭卡扣费,每次写入都能从串口打印看到扣费数值和卡内当前金额,如扣费金额的结果会的低于设定的0,则会进行报错提示,扣费指令如下

cpp 复制代码
FE 02 52 00 00 00 00 02 10 30

实验结果如图

优化建议

分享的部分代码仅作为参考,对于案例的优化部分,在串口通讯协议的地方,0x02的命令类型和0x52的标准卡查询为固定的帧内容,RFID的序列号也无需加在协议帧中,在代码中防碰撞读出序列号后可保存在某个数组中继续进行后续操作,优化后的串口指令部分为

至于代码方面也可以按不同需求简化代码,减少代码量